Introduction

Accurately predicting home prices is essential for stakeholders, such as buyers, investors, and policymakers, as it includes important financial decisions, urban planning, and policy making initiatives. However, home price predictions remain a difficult tasks, as they are influenced by a wide range of factors, including the housing characteristics, neighborhood amenities, and the overall neighborhood economic vibration. In this analysis, I build a predictive model for home prices in Charlotte, NC, using a dataset of housing information in Mecklenburg County, NC. The model will be based on the internal and external or spatial factors to predict the housing prices. The internal factors may include some housing basic characteristics, like footage, number of bedrooms, etc. The external factors may include the crime rate, median household income, the coverage of the public transportation,and access to the park and public spaces. By identifying and engineering such factors, our predictive model aims to minimize the prediction errors and provides deeper insights into the housing market in Charlotte, NC.

My analytical approach start with loading and cleaning the providing existing housing datasets. I first explored the datasets and identify the missing values and potential outliers. Then, utilizing the feature engineering process, I created new features from the existing features to improve the model performance, including the home ages and the dummy variables for the air conditioning and heating system. Additionally, spatial data and features were gathered from the American Community Survey, and open data portal, including crimes data, proximity to recreational spaces, and tree canopy. Following data preparation, I partitioned the data into training and testing subsets using an 80-20 split to reliably assess the model’s predictive performance. The model was developed using Ordinary Least Squares (OLS) regression and tested across various combinations of variables. The final model was evaluated using diagnostic metrics (MAE, RMSE, R-squared) and spatial residual analyses, including Moran’s I test, to detect spatial autocorrelation in residuals. The analysis concludes by addressing model limitations and potential to improvements.

Data Collection & Exploring

In this section, I clean the existing housing datasets and explore other sources of data that are important for the model building process. Second, I check the correlation between the variables to identify the multicollinearity between the variables, the linearity relationship between key predictors and the dependent variable, and the visual sense of autocorrelation in the data.

Step 1: Load and Clean the Data

The first step for this analysis start by loading the household data of the Charlotte, NC area. The data is in the geojson format with over 46,000 records of the housing information in Mecklenburg County, NC.

house<- st_read("data/studentData.geojson")

The original dataset contain many information that are not closely related with the housing prices, such as zip code, owner’s name, tax code. I filtered the datasets that only contains information about the price, bedrooms, yearbuilt, fullbaths, halfbaths, heatedarea, storyheigh, numfirepla, bldggrade, units, heatedfuel,and actype of the houses. The data is loaded and cleaned by removing the missing values and the houses with price less than 0. For prediction purposes, the price equal or below 0 is not useful.

house<- house %>%
  dplyr::select(price, bedrooms,yearbuilt,fullbaths, halfbaths, heatedarea, storyheigh, numfirepla, bldggrade,units, heatedfuel, actype)  %>%
  filter(!is.na(price) & !is.na(bedrooms) & !is.na(yearbuilt) & !is.na(fullbaths) & !is.na(halfbaths) & !is.na(heatedarea) & !is.na(storyheigh) & !is.na(numfirepla) & !is.na(bldggrade) & !is.na(units) & !is.na(heatedfuel) & !is.na(actype))%>%
  filter(price>0)

After checking housing prices, a small number of homes in Charlotte have exceptionally high values, representing clear outliers. These extreme values could significantly distort our model’s ability to accurately predict typical housing prices. Therefore, I have excluded all homes valued above 10 million dollars to ensure the robustness and accuracy of our predictive model.

house<- house %>%
  filter(price<10000000)

After the primary filtering, the dataset contains about 35,000 records of housing information in Mecklenburg County, NC.

Check the skewness of the variables

For the prediction purpose, the normal distribution of the variable matters because it helps ensure the accuracy and reliability of the model. When the variable is normally distributed, it usually means that the erroes or residuals are also normally distributed.

The histogram of the price shows that the price is right skewed. To make the price more normally distributed, I took the log transformation of the price. The histogram of the log price shows that the log price is more normally distributed than the original price. I used the log price as the dependent variable in the model building process.

ggplot(house, aes(x = price)) +
  geom_histogram(fill = "lightblue", bins= 500) +
  labs(title = "Histogram of Price", x = "Price", y = "Frequency")

house <- house %>%
  mutate(logprice = log(price))

ggplot(house, aes(x = logprice)) +
  geom_histogram(fill = "lightblue", bins= 500) +
  labs(title = "Histogram of Log Price", x = "Log Price", y = "Frequency")

Step 2: Feature engineering

The feature engineering is the process of creating new features from the existing features. In this analysis, I created the age of the house, from the housing built year. I also created the dummy variables for the air conditioning and heating system, and the categorical variable housing type. The dummy variables for the air conditioning and heating system are created by checking if the house has air conditioning or heating system, as housing with air conditioning and heating system tends to have higher price. The type variable is created by categorizing the house as mansion, castle and normal house. The house has more than 5000 square feet of heated area, more than 3 bedrooms, and more than 3 full baths ia categorized as mansion. The house has more than 10000 square feet of heated area, more than 5 bedrooms, and more than 5 full baths is categorized as castle. The rest of the houses are categorized as normal house.

The building grade is converted to a numeric variable by assigning a number to each category based on factors. The new features are created to improve the model performance.

Grade Description Typical Examples Quality & Value Level
Minimum Very basic, minimal standards,limited durability. Old sheds, simple outbuildings, deteriorating structures Lowest
Fair Below-average condition, noticeable wear or deferred maintenance. Older homes/apartments with deferred maintenance, structures needing renovation Below Average
Average Standard construction quality, functional and maintained condition. Typical single-family homes, standard apartment units Moderate
Good Above-average quality construction, good condition. Newer suburban homes, renovated units, higher-quality commercial buildings Above Average
Excellent Superior quality, exceptional finishes, and meticulous attention to detail. High-end residences, luxury condominiums, upscale commercial properties High
Custom Unique or specialized construction, highly personalized design,typically luxury or architecturally significant. Luxury custom homes, unique historical restorations, architect-designed high-end properties Highest or Specialized

Source: https://localdocs.charlotte.edu/Tax_Collections/Reports_Studies/

house <- house %>%
  mutate(
    age = 2022 - yearbuilt,
    storyheigh = as.numeric(gsub("[^0-9.]", "", storyheigh)),
    bldggrade = case_when(
      bldggrade == "MINIMUM" ~ 1,
      bldggrade == "FAIR" ~ 2,
      bldggrade == "AVERAGE" ~ 3,
      bldggrade == "GOOD" ~ 4,
      bldggrade == "VERY GOOD" ~ 5,
      bldggrade == "EXCELLENT" ~ 6,
      bldggrade == "CUSTOM" ~ 7),
    ac_dummy = ifelse(actype == "AC-NONE", 0, 1),
    heat_dummy = ifelse(heatedfuel == "NONE", 0, 1),
    mansion= ifelse(heatedarea>5000 & heatedarea<10001 & bedrooms>2 & bedrooms<6 & fullbaths>2, 1, 0),
    castle= ifelse(heatedarea>10000 & bedrooms>5 & fullbaths>5, 1, 0),
    house= ifelse(mansion==0 & castle==0, 1, 0),
    heatfuel = ifelse(heatedfuel == "NONE", NA, heatedfuel)
    )

house <- house %>%
  dplyr::select(-yearbuilt, -actype, -heatedfuel)%>%
  mutate(type= case_when(
    house==1 ~ "House",
    mansion==1 ~ "Mansion",
    castle==1 ~ "Castle"
  ))

Additional spatial data to predict the housing price

Many other factors can affect the housing price that not contains in the given datasets. Many other spatial variables affect the housing price and value as well. For example, the housing values are closely correlate with the crime rates in the neighborhood, median household income and whether the house is close to the public transportation and access to the park and public spaces. All those variables should be included in the prediction model to improve the model performance.

The additional spatial data is collected from American Community Survey, City of Charlotte open data portal and other sources. The additional spatial data includes the crime rate, the distance to the public transportation, the distance to the park. The additional spatial data is collected and cleaned to be used in the model building process.

Median household income data was collected from the American Community Survey at the census block group level. The median household income values were then assigned to individual housing data points based on their geographic location.

income<- get_acs(
  geography = "block group",
  variables = c("income"="B19013_001"),
  state = "NC",
  county = "Mecklenburg",
  geometry = TRUE
  )%>%
  st_transform(crs =st_crs(house))%>%
  filter(!is.na(estimate))%>%
  dplyr::select(estimate, GEOID, geometry)%>%
  rename(income=estimate)

#spatial join the income data to the house data
house <- st_join(house, income)%>%
  filter(!is.na(income))

Crime data was collected from the City of Charlotte open data portal from police station. And then, the crime data is aggregated at the census block group level same as the household income data. Finally, the crime data was spatially joined to the house data based on the house geolocation.

crime<- read.csv("data/CMPD.csv")%>%
  filter(YEAR>=2021)

crime<- crime %>%
  dplyr::select(LATITUDE_PUBLIC, LONGITUDE_PUBLIC, YEAR)%>%
  st_as_sf(coords = c("LONGITUDE_PUBLIC", "LATITUDE_PUBLIC"), crs = 4326)%>%
  st_transform(crime, crs =st_crs(house))

crime_tract<- st_join(house, crime)%>%
  group_by(GEOID)%>%
  summarise(crime=n())

house <- st_join(house, crime_tract)%>%
  dplyr::select(-GEOID.x, -GEOID.y)%>%
  filter(!is.na(crime))

Public transportation coverage data was collected from the Charlotte/Mecklenburg Quality of Life Explorer. This dataset measures the percentage of housing units located within 0.5 miles of a transit stop at the neighborhood level as defined by Mecklenburg County. The neighborhood-level transit coverage values were then assigned to individual housing data points based on their geographic location.

transit <- st_read("data/transportation.geojson")%>%
  dplyr::select(X2023, geometry)%>%
  rename(transit=X2023)

#spatial join the transit data to the house data
house <- st_join(house, transit)%>%
  filter(!is.na(transit))

Tree Canopy data was collected from the Charlotte/Mecklenburg Quality of Life Explorer. This dataset represents the percentage of tree cover in each neighborhood. Like the transit coverage data, these values are linked to the housing data based on each property’s geolocation.

tree <- st_read("data/tree_canopy.geojson")%>%
  dplyr::select(X2023, geometry)%>%
  rename(canopy=X2023)

#spatial join the tree canopy data to the house data
house <- st_join(house, tree)

Approximate distance to the park data was collected from the Charlotte/Mecklenburg Quality of Life Explorer. This dataset measures the percentage of housing units within 0.5 miles of an outdoor public recreation area at the neighborhood level. Similar to other spatial datasets, the park accessibility data was assigned to individual housing records based on their geographic locations.

recreation<- st_read("data/recreation.geojson")%>%
  dplyr::select(X2023, geometry)%>%
  rename(recreation=X2023)

#spatial join the recreation data to the house data
house <- st_join(house, recreation)%>%
  filter(!is.na(recreation))

The potential drawback of these approaches is that the data are aggregated at large geographic units rather than at the individual household level. Households within the same census block group or neighborhood may differ in their access to transit and other services. Additionally, households within the same neighborhood may vary significantly in income levels. Despite these limitations, the aggregated data represent the best available source for use in the model-building process.

Step 3: Summary Statistics and Correlation Matrix

The summary statistics and correlation matrix are calculated to understand the relationship between the variables. The summary statistics show the mean and standard deviation (SD) of the variables. The correlation matrix shows the correlation between the variables. The correlation matrix is used to understand the relationship between the variables and to identify the multicollinearity between the variables. Lastly, various scatter plots are created to visualize the relationship between the key predictors and the dependent variable to ensure the linearity relationship between the predictors and the dependent variable.

Summary Statistics

For the summary statistics, I calculated the mean and standard deviation of the variables. The summary statistics are calculated to understand the distribution of the variables. The summary statistics are used to understand the distribution of the variables and to identify the outliers in the data.

dependent_var <- "logprice"

predictors <- c("bedrooms", "fullbaths", "halfbaths", "heatedarea", "storyheigh", "numfirepla", "bldggrade", "units", "age", "income", "crime", "transit", "canopy", "recreation")

summary_stats <- house %>%
  st_drop_geometry() %>%
  dplyr::select(all_of(c(dependent_var, predictors))) %>%
  summarise_all(list(Mean = mean, SD = sd), na.rm = TRUE) %>%
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Value") %>%
  separate(Variable, into = c("Variable", "Stat"), sep = "_") %>%
  pivot_wider(names_from = Stat, values_from = Value)



summary_stats <- summary_stats %>%
  mutate(Variable = case_when(
    Variable == "logprice" ~ "Log transformed House Value",
    Variable == "bedrooms" ~ "Number of bedrooms",
    Variable == "fullbaths" ~ "Number of full baths",
    Variable == "halfbaths" ~ "Number of half baths",
    Variable == "heatedarea" ~ "House Square footage",
    Variable == "storyheigh" ~ "Number of stories",
    Variable == "numfirepla" ~ "Number of fireplaces",
    Variable == "bldggrade" ~ "Building Grade",
    Variable == "units" ~ "Number of units",
    Variable == "age" ~ "Age of the house",
    Variable == "income" ~ "Median household income",
    Variable == "crime" ~ "Number of Crime in Census Block group",
    Variable == "transit" ~ "% Transit Coverage",
    Variable == "canopy" ~ "% Tree Canopy Coverage",
    Variable == "recreation" ~ "% Recreation Coverage",
    TRUE ~ Variable
  ))




summary_stats <- summary_stats %>%
  mutate(
    Mean = round(Mean, 2),
    SD = round(SD, 2)
  )

summary_stats <- summary_stats %>%
  arrange(Variable == "Log transformed House Value")

predictor_rows <- which(summary_stats$Variable != "Log transformed House Value")
dependent_rows <- which(summary_stats$Variable == "Log transformed House Value")

# Determine the start and end rows for each group
start_pred <- min(predictor_rows)
end_pred   <- max(predictor_rows)
start_dep  <- min(dependent_rows)
end_dep    <- max(dependent_rows)

# Create the table using kable and add extra formatting
kable(summary_stats, caption = "Summary Statistics",
      align = c("l", "l", "l"), booktabs = TRUE, escape = FALSE ) %>%
  add_header_above(c(" " = 1, "Statistics" = 2)) %>%
  kable_styling(full_width = FALSE) %>%
  group_rows("Predictors", start_pred, end_pred) %>%
  group_rows("Dependent Variable", start_dep, end_dep)%>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = TRUE)
Summary Statistics
Statistics
Variable Mean SD
Predictors
Number of bedrooms 3.52 0.91
Number of full baths 2.29 0.83
Number of half baths 0.60 0.53
House Square footage 2366.73 1057.49
Number of stories 1.65 0.48
Number of fireplaces 0.79 0.47
Building Grade 3.43 0.78
Number of units 0.98 0.19
Age of the house 28.26 24.04
Median household income 111240.12 50146.79
Number of Crime in Census Block group 156.21 144.64
% Transit Coverage 52.13 39.83
% Tree Canopy Coverage 50.31 14.57
% Recreation Coverage 55.12 33.92
Dependent Variable
Log transformed House Value 12.88 0.54

Correlation Matrix

predictor_vars <- house[, c("bedrooms", "fullbaths", "halfbaths", "heatedarea", "storyheigh", "numfirepla", "bldggrade", "units", "age", "ac_dummy", "heat_dummy", "income", "crime", "transit", "canopy", "recreation")]%>%st_drop_geometry()


cor_matrix <- cor(predictor_vars, use = "complete.obs", method = "pearson")

ggcorrplot(cor_matrix,
           method = "square",
           type = "lower",
           lab = TRUE,
           lab_size = 3,
           colors = c("#a4133c", "white", "#a4133c"))+
    labs(title = "Correlation Matrix for all Predictor Variables") +
    theme(plot.subtitle = element_text(size = 9, face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        axis.text.x = element_text(size = 7),
        axis.text.y = element_text(size = 7),
        axis.title = element_text(size = 8))

Since there are high correlation between the heated area and full baths, I will remove the full baths from the model building process. The full baths is removed to avoid the multicollinearity between the variables. Similarly, the number of bedrooms and heated area are highly correlated, I will remove the number of bedroom a from the model building process. Storyheight is also removed for the same reason.

After removing, the final correlation matrix is following:

predictor_vars <- house[, c( "halfbaths", "heatedarea", "numfirepla", "bldggrade", "units", "age", "ac_dummy", "heat_dummy", "income", "crime", "transit", "canopy", "recreation")]%>%st_drop_geometry()


cor_matrix <- cor(predictor_vars, use = "complete.obs", method = "pearson")

ggcorrplot(cor_matrix,
           method = "square",
           type = "lower",
           lab = TRUE,
           lab_size = 3,
           colors = c("#a4133c", "white", "#a4133c"))+
    labs(title = "Correlation Matrix for all Predictor Variables") +
    theme(plot.subtitle = element_text(size = 9, face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        axis.text.x = element_text(size = 7),
        axis.text.y = element_text(size = 7),
        axis.title = element_text(size = 8))

house<- house %>%
  dplyr::select(-fullbaths, -storyheigh, -bedrooms)

Check the VIF

To ensure the multicollinearity between the variables, I checked the Variance Inflation Factor (VIF) of the variables. The VIF values are calculated to further check the multicollinearity between the variables. The VIF values are used to identify the multicollinearity between the variables. The VIF values above 5 indicate high multicollinearity between the variables. The VIF values are calculated to ensure the multicollinearity between the variables. The VIF values for all the variables in the model are below 3, which indicates that there is no multicollinearity between the variables.

model<- lm(logprice ~ halfbaths + heatedarea + numfirepla + bldggrade + units + age + ac_dummy + heat_dummy + income + crime + transit + canopy + recreation, data = house)

# Compute VIF values
vif_values <- vif(model)

# Convert the VIF values to a data frame
vif_df <- data.frame(Variable = names(vif_values),
                     VIF = as.vector(vif_values))

# Display the VIF table using knitr::kable
knitr::kable(vif_df, caption = "Variance Inflation Factors for the Model")
Variance Inflation Factors for the Model
Variable VIF
halfbaths 1.199884
heatedarea 2.448252
numfirepla 1.249302
bldggrade 2.023239
units 1.063245
age 1.727478
ac_dummy 1.176752
heat_dummy 1.037838
income 1.784013
crime 1.604203
transit 1.942375
canopy 1.382799
recreation 1.364279

Scatterplot

longer<-house %>%
  pivot_longer(cols = c(
    "heatedarea",
    "age",
    "crime",
    "transit"),
               names_to = "Variable",
               values_to = "Value")%>%
  st_drop_geometry()

ggplot(longer,aes(x = Value, y = price)) +
  geom_point(color = "black", size= 0.4) +
  geom_smooth(method = "lm", color = "red", se = FALSE) +
  facet_wrap(~ Variable, scales = "free", labeller = as_labeller(c("heatedarea" = "Heated Area",
                                                                  "age" = "Age of the House",
                                                                  "crime" = "Number of Crime",
                                                                  "transit" = "Transit Coverage"
  )))  +
  theme_light() +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6),
        axis.title=element_text(size=8)) +
  labs(title = "Scatter Plots of Dependent Variable vs. Predictors",
       x = "Value",
       y = "House price")

Step 4: Mapping

Dependent Variable- Housing price

map<-st_join(house, income)%>%
  group_by(GEOID)%>%
  summarise(price=mean(price))%>%
  st_drop_geometry()

map<- left_join(income, map, by="GEOID")%>%
  filter(!is.na(price))


ggplot(map) +
  geom_sf(aes(fill= q5(price)), color='white') +
  scale_fill_manual(values = flatreds5,
                     labels = function(x) round(as.numeric(qBr(map, 'price')), 0),
                     name = 'Housing price') +
  labs(title = "Average House Price by Census Block Group",
       fill = "Price") +
    theme(
        legend.text = element_text(size = 8),
        legend.title = element_text(size = 10),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        plot.subtitle = element_text(size = 10, face = "italic"),
        plot.title = element_text(size = 20, hjust= 0.5,face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill = NA, size = 0.8))

Key predictors

Q1<-ggplot(map) +
  geom_sf(aes(fill= q5(income)), color='white') +
  scale_fill_manual(values = flatreds5,
                     labels = function(x) round(as.numeric(qBr(map, 'income')), 0),
                     name = 'Income') +
  labs(title = "Average Household Income by Census Block Group",
       fill = "Income") +
    theme(
        legend.text = element_text(size = 4),
        legend.title = element_text(size = 5),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        plot.subtitle = element_text(size = 5, face = "italic"),
        plot.title = element_text(size = 10, hjust= 0.5,face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill = NA, size = 0.8))

Q2<-ggplot(tree) +
  geom_sf(aes(fill= q5(canopy)), color='white') +
  scale_fill_manual(values = flatreds5,
                     labels = function(x) round(as.numeric(qBr(tree, 'canopy')), 0),
                     name = '% of Tree Coverage') +
  labs(title = "Tree Canopy by Neighborhood",
       fill = "% of Tree Coverage") +
    theme(
        legend.text = element_text(size =4),
        legend.title = element_text(size = 5),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        plot.subtitle = element_text(size = 5, face = "italic"),
        plot.title = element_text(size = 10, hjust= 0.5,face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill = NA, size = 0.8))

recreation<-recreation%>% filter(!is.na(recreation))
Q3<-ggplot(recreation) +
  geom_sf(aes(fill= q5(recreation)), color='white') +
  scale_fill_manual(values = flatreds5,
                     labels = function(x) round(as.numeric(qBr(tree, 'canopy')), 0),
                     name = '% of Recreation Coverage') +
  labs(title = "Recreation facilities coverage by Neighborhood",
       fill = "% of Recreational facilities Coverage") +
    theme(
        legend.text = element_text(size = 4),
        legend.title = element_text(size = 5),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        plot.subtitle = element_text(size = 5, face = "italic"),
        plot.title = element_text(size = 10, hjust= 0.5,face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill = NA, size = 0.8))

transit<-transit%>% filter(!is.na(transit))

Q4<-ggplot(transit) +
  geom_sf(aes(fill= q5(transit)), color='white') +
  scale_fill_manual(values = flatreds5,
                     labels = function(x) round(as.numeric(qBr(transit, 'transit')), 0),
                     name = '% of Transit Coverage') +
  labs(title = "Public Transit coverage by Neighborhood",
       fill = "% of Public Transit Coverage") +
    theme(
        legend.text = element_text(size = 4),
        legend.title = element_text(size = 5),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        plot.subtitle = element_text(size = 5, face = "italic"),
        plot.title = element_text(size = 10, hjust= 0.5,face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill = NA, size = 0.8))

# Arrange the plots in a 2x2 grid
grid.arrange(Q1, Q2, Q3, Q4, ncol = 2)

Model Building & Evaluation

Train-Test Split

In this model building process, I spilt the training and testing datasets based on 80% training and 20% testing to ensure the model have a better predictivity

# Set the seed for reproducibility
set.seed(123)

# Split the data into training and testing sets
train_index <- createDataPartition(house$price, p = 0.8, list = FALSE)

train <- house[train_index, ]
test <- house[-train_index, ]

Model Building and Evaluation

Initial model

model1 <- lm(logprice ~ halfbaths + heatedarea + numfirepla + bldggrade + units + age+ income + crime + transit + canopy + recreation, data = train)

test<-test%>%
  mutate(predictions = predict(model1, newdata = test),
         actual= logprice,
         error= actual - predictions,
         abserror= abs(error),
         ape=(abs(actual-predictions)) / actual)

# Calculate Mean Absolute Error (MAE)
mae <- mean(test$abserror)

# Calculate Root Mean Squared Error (RMSE)
rmse <- sqrt(mean((test$actual - test$predictions)^2))

# Calculate R-squared for the test set
sse <- sum((test$actual - test$predictions)^2)
sst <- sum((test$actual - mean(test$actual))^2)
r2 <- 1 - sse/sst

# Print the results
results <- data.frame(
  Metric = c("Mean Absolute Error (MAE)", "Root Mean Squared Error (RMSE)", "Test R-squared"),
  Value = c(round(mae, 5), round(rmse, 5), round(r2, 5))
)

# Print results using kable
kable(results, caption = "Model Performance Metrics")
Model Performance Metrics
Metric Value
Mean Absolute Error (MAE) 0.21552
Root Mean Squared Error (RMSE) 0.30249
Test R-squared 0.68026

Include the dummy variables

model2 <- lm(logprice ~ halfbaths + heatedarea + numfirepla + bldggrade + units + age + ac_dummy + heat_dummy + income + crime + transit + canopy + recreation, data = train)

test<-test%>%
  mutate(predictions = predict(model2, newdata = test),
         actual= logprice,
         error= actual - predictions,
         abserror= abs(error),
         ape=(abs(actual-predictions)) / actual)

# Calculate Mean Absolute Error (MAE)
mae <- mean(test$abserror)

# Calculate Root Mean Squared Error (RMSE)
rmse <- sqrt(mean((test$actual - test$predictions)^2))

# Calculate R-squared for the test set
sse <- sum((test$actual - test$predictions)^2)
sst <- sum((test$actual - mean(test$actual))^2)
r2 <- 1 - sse/sst

# Print the results
results <- data.frame(
  Metric = c("Mean Absolute Error (MAE)", "Root Mean Squared Error (RMSE)", "Test R-squared"),
  Value = c(round(mae, 5), round(rmse, 5), round(r2, 5))
)

# Print results using kable
kable(results, caption = "Model Performance Metrics")
Model Performance Metrics
Metric Value
Mean Absolute Error (MAE) 0.21518
Root Mean Squared Error (RMSE) 0.30156
Test R-squared 0.68221

Include the Categorical Variables

model3 <- lm(logprice ~ halfbaths + heatedarea + numfirepla + bldggrade + units + age+ income + crime + transit + canopy + recreation + type, data = train)

test<-test%>%
  mutate(predictions = predict(model3, newdata = test),
         actual= logprice,
         error= actual - predictions,
         abserror= abs(error),
         ape=(abs(actual-predictions)) / actual)

# Calculate Mean Absolute Error (MAE)
mae <- mean(test$abserror)

# Calculate Root Mean Squared Error (RMSE)
rmse <- sqrt(mean((test$actual - test$predictions)^2))

# Calculate R-squared for the test set
sse <- sum((test$actual - test$predictions)^2)
sst <- sum((test$actual - mean(test$actual))^2)
r2 <- 1 - sse/sst

# Print the results
results <- data.frame(
  Metric = c("Mean Absolute Error (MAE)", "Root Mean Squared Error (RMSE)", "Test R-squared"),
  Value = c(round(mae, 5), round(rmse, 5), round(r2, 5))
)

# Print results using kable
kable(results, caption = "Model Performance Metrics")
Model Performance Metrics
Metric Value
Mean Absolute Error (MAE) 0.21529
Root Mean Squared Error (RMSE) 0.30205
Test R-squared 0.68118

Final model

model4 <- lm(logprice ~  heatedarea + numfirepla+ bldggrade + units + age+ income + crime + transit + canopy + type+ ac_dummy + heat_dummy, data = train)
summary(model4)
## 
## Call:
## lm(formula = logprice ~ heatedarea + numfirepla + bldggrade + 
##     units + age + income + crime + transit + canopy + type + 
##     ac_dummy + heat_dummy, data = train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.4978 -0.1626  0.0106  0.1760  3.1461 
## 
## Coefficients:
##                   Estimate     Std. Error t value             Pr(>|t|)    
## (Intercept) 10.36348279816  0.16726830837  61.957 < 0.0000000000000002 ***
## heatedarea   0.00021952749  0.00000288363  76.129 < 0.0000000000000002 ***
## numfirepla   0.05547169198  0.00450513909  12.313 < 0.0000000000000002 ***
## bldggrade    0.23680234526  0.00343081975  69.022 < 0.0000000000000002 ***
## units       -0.08285680814  0.00993145730  -8.343 < 0.0000000000000002 ***
## age          0.00174947866  0.00010033112  17.437 < 0.0000000000000002 ***
## income       0.00000230146  0.00000004984  46.176 < 0.0000000000000002 ***
## crime       -0.00012404294  0.00001618311  -7.665   0.0000000000000185 ***
## transit      0.00095080578  0.00006181952  15.380 < 0.0000000000000002 ***
## canopy      -0.00263274567  0.00015069211 -17.471 < 0.0000000000000002 ***
## typeHouse    0.87181193077  0.15862322298   5.496   0.0000000391595998 ***
## typeMansion  0.75267648694  0.15851853081   4.748   0.0000020624871705 ***
## ac_dummy     0.19300153324  0.01130432411  17.073 < 0.0000000000000002 ***
## heat_dummy  -0.03969739237  0.05069505071  -0.783                0.434    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.3144 on 28242 degrees of freedom
## Multiple R-squared:  0.6646, Adjusted R-squared:  0.6644 
## F-statistic:  4305 on 13 and 28242 DF,  p-value: < 0.00000000000000022
test<-test%>%
  mutate(predictions = predict(model4, newdata = test),
         actual= logprice,
         error= actual - predictions,
         abserror= abs(error),
         ape=(abs(actual-predictions)) / actual)

# Calculate Mean Absolute Error (MAE)
mae <- mean(test$abserror)

# Calculate Root Mean Squared Error (RMSE)
rmse <- sqrt(mean((test$actual - test$predictions)^2))

# Calculate R-squared for the test set
sse <- sum((test$actual - test$predictions)^2)
sst <- sum((test$actual - mean(test$actual))^2)
r2 <- 1 - sse/sst

# Print the results
results <- data.frame(
  Metric = c("Mean Absolute Error (MAE)", "Root Mean Squared Error (RMSE)", "Test R-squared"),
  Value = c(round(mae, 5), round(rmse, 5), round(r2, 5))
)

# Print results using kable
kable(results, caption = "Model Performance Metrics")
Model Performance Metrics
Metric Value
Mean Absolute Error (MAE) 0.21505
Root Mean Squared Error (RMSE) 0.30126
Test R-squared 0.68285

model diagnostics

par(mfrow = c(2, 2))
plot(model4)

Compare with non log transformed price model

model5 <- lm(price ~  heatedarea + numfirepla+ bldggrade + units + age+ income + crime + transit + canopy + type+ ac_dummy + heat_dummy, data = train)

par(mfrow = c(2, 2))
plot(model5)

Spatial Analysis & Residual Diagnostics

model_final<-lm(logprice ~  heatedarea + numfirepla+ bldggrade + units + age+ income + crime + transit + canopy + type+ ac_dummy + heat_dummy, data = house)

fitted_values <- fitted(model_final)
residuals <- residuals(model_final)

house<- house %>%
  mutate(fitted = fitted_values,
         residuals = residuals)

ggplot(house, aes(x = fitted, y = logprice)) +
  geom_point(color = "black", size = 0.5) +
  geom_smooth(method = "lm", color = "red", se = FALSE) +
  labs(title = "Predicted Values vs. Observed Values",
       x = "Observed Values",
       y = "Predicted Values") +
  theme_light() +
  theme(plot.subtitle = element_text(size = 9, face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        axis.text.x = element_text(size = 7),
        axis.text.y = element_text(size = 7),
        axis.title = element_text(size = 8))

residual_map<-st_join(income, house)%>%
  group_by(GEOID)%>%
  summarise(residual=mean(residuals))%>%
  filter(!is.na(residual))

ggplot(residual_map) +
  geom_sf(aes(fill= q5(residual)), color='white') +
  scale_fill_manual(values = flatreds5,
                     labels = function(x) round(as.numeric(qBr(residual_map, 'residual')), 6),
                     name = 'Residuals') +
  labs(title = "Residuals Spatial Distribution",
       fill = "Residuals") +
    theme(
        legend.text = element_text(size =8),
        legend.title = element_text(size = 10),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        plot.subtitle = element_text(size = 8, face = "italic"),
        plot.title = element_text(size = 20, hjust= 0.5,face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill = NA, size = 0.8))

# Extract the coordinates from the spatial dataframe
coords <- st_coordinates(test) 

# Create a neighbor list using k-nearest neighbors (KNN) with k=5
neighborList <- knn2nb(knearneigh(coords, 5))

# Convert the neighbor list to a spatial weights matrix
spatialWeights <- nb2listw(neighborList, style="W")

moran <- moran.mc(test$error, spatialWeights, nsim = 999)

ggplot(as.data.frame(moran$res[c(1:999)]), aes(moran$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01) +
  geom_vline(aes(xintercept = moran$statistic), colour = "#FA7800",size=1) +
  scale_x_continuous(limits = c(-1, 1)) +
  labs(title="Observed and permuted Moran's I",
       subtitle= "Observed Moran's I in orange",
       x="Moran's I",
       y="Count") +
  theme_minimal()

# Extract the coordinates from the spatial dataframe
coords <- st_coordinates(house) 

# Create a neighbor list using k-nearest neighbors (KNN) with k=5
neighborList <- knn2nb(knearneigh(coords, 5))

# Convert the neighbor list to a spatial weights matrix
spatialWeights <- nb2listw(neighborList, style="W")

moran <- moran.mc(house$residuals, spatialWeights, nsim = 999)

ggplot(as.data.frame(moran$res[c(1:999)]), aes(moran$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01) +
  geom_vline(aes(xintercept = moran$statistic), colour = "#FA7800",size=1) +
  scale_x_continuous(limits = c(-1, 1)) +
  labs(title="Observed and permuted Moran's I",
       subtitle= "Observed Moran's I in orange",
       x="Moran's I",
       y="Count") +
  theme_minimal()

Conclusion

LS0tCnRpdGxlOiAnTVVTQSA1MDgwIE1pZHRlcm06IFByZWRpY3RpdmUgbW9kZWwgZm9yIGhvbWUgcHJpY2VzIGluIENoYXJsb3R0ZSwgTkMnCmF1dGhvcjogIlpoYW5jaGFvIFlhbmciCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogam91cm5hbAogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgdG9jOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKb3B0aW9ucyhzY2lwZW4gPSA5OTkpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGdnY29ycnBsb3QpCmxpYnJhcnkoc2YpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShjYXIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoTUFTUykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3BkZXApICNmb3Igc3BhdGlhbCBhdXRvY29ycmVsYXRpb24KbGlicmFyeShjYXJldCkKbGlicmFyeShja2FucikKbGlicmFyeShGTk4pCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZ2djb3JycGxvdCkKbGlicmFyeShqdG9vbHMpICAgICAjIGZvciByZWdyZXNzaW9uIG1vZGVsIHBsb3RzCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkodHVmdGUpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmBgYAoKYGBge3IsIGluY2x1ZGU9RkFMU0V9CiMgTWFwIGVsZW1lbnQKcTUgPC0gZnVuY3Rpb24odmFyaWFibGUpIHthcy5mYWN0b3IobnRpbGUodmFyaWFibGUsIDUpKX0KZmxhdHJlZHM1IDwtIGMoIiMwMDgxYTciLCAgIiMwMGFmYjkiLCAiI2ZkZmNkYyIsICIjZmVkOWI3IiwgIiNmMDcxNjciKQpxQnIgPC0gZnVuY3Rpb24oZGYsIHZhcmlhYmxlLCBybmQpIHsKICBpZiAobWlzc2luZyhybmQpKSB7CiAgICBhcy5jaGFyYWN0ZXIocXVhbnRpbGUocm91bmQoZGZbW3ZhcmlhYmxlXV0sMyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYyguMDEsLjIsLjQsLjYsLjgpLCBuYS5ybT1UKSkKICB9IGVsc2UgaWYgKHJuZCA9PSBGQUxTRSB8IHJuZCA9PSBGKSB7CiAgICBhcy5jaGFyYWN0ZXIoZm9ybWF0QyhxdWFudGlsZShkZltbdmFyaWFibGVdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoLjAxLC4yLC40LC42LC44KSwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICBkaWdpdHMgPSAzKSkKICB9Cn0KYGBgCgojIEludHJvZHVjdGlvbgpBY2N1cmF0ZWx5IHByZWRpY3RpbmcgaG9tZSBwcmljZXMgaXMgZXNzZW50aWFsIGZvciBzdGFrZWhvbGRlcnMsIHN1Y2ggYXMgYnV5ZXJzLCBpbnZlc3RvcnMsIGFuZCBwb2xpY3ltYWtlcnMsIGFzIGl0IGluY2x1ZGVzIGltcG9ydGFudCBmaW5hbmNpYWwgZGVjaXNpb25zLCB1cmJhbiBwbGFubmluZywgYW5kIHBvbGljeSBtYWtpbmcgaW5pdGlhdGl2ZXMuIEhvd2V2ZXIsIGhvbWUgcHJpY2UgcHJlZGljdGlvbnMgcmVtYWluIGEgZGlmZmljdWx0IHRhc2tzLCBhcyB0aGV5IGFyZSBpbmZsdWVuY2VkIGJ5IGEgd2lkZSByYW5nZSBvZiBmYWN0b3JzLCBpbmNsdWRpbmcgdGhlIGhvdXNpbmcgY2hhcmFjdGVyaXN0aWNzLCBuZWlnaGJvcmhvb2QgYW1lbml0aWVzLCBhbmQgdGhlIG92ZXJhbGwgbmVpZ2hib3Job29kIGVjb25vbWljIHZpYnJhdGlvbi4gSW4gdGhpcyBhbmFseXNpcywgSSBidWlsZCBhIHByZWRpY3RpdmUgbW9kZWwgZm9yIGhvbWUgcHJpY2VzIGluIENoYXJsb3R0ZSwgTkMsIHVzaW5nIGEgZGF0YXNldCBvZiBob3VzaW5nIGluZm9ybWF0aW9uIGluIE1lY2tsZW5idXJnIENvdW50eSwgTkMuIFRoZSBtb2RlbCB3aWxsIGJlIGJhc2VkIG9uIHRoZSBpbnRlcm5hbCBhbmQgZXh0ZXJuYWwgb3Igc3BhdGlhbCBmYWN0b3JzIHRvIHByZWRpY3QgdGhlIGhvdXNpbmcgcHJpY2VzLiBUaGUgaW50ZXJuYWwgZmFjdG9ycyBtYXkgaW5jbHVkZSBzb21lIGhvdXNpbmcgYmFzaWMgY2hhcmFjdGVyaXN0aWNzLCBsaWtlIGZvb3RhZ2UsIG51bWJlciBvZiBiZWRyb29tcywgZXRjLiBUaGUgZXh0ZXJuYWwgZmFjdG9ycyBtYXkgaW5jbHVkZSB0aGUgY3JpbWUgcmF0ZSwgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUsIHRoZSBjb3ZlcmFnZSBvZiB0aGUgcHVibGljIHRyYW5zcG9ydGF0aW9uLGFuZCBhY2Nlc3MgdG8gdGhlIHBhcmsgYW5kIHB1YmxpYyBzcGFjZXMuIEJ5IGlkZW50aWZ5aW5nIGFuZCBlbmdpbmVlcmluZyBzdWNoIGZhY3RvcnMsIG91ciBwcmVkaWN0aXZlIG1vZGVsIGFpbXMgdG8gbWluaW1pemUgdGhlIHByZWRpY3Rpb24gZXJyb3JzIGFuZCBwcm92aWRlcyBkZWVwZXIgaW5zaWdodHMgaW50byB0aGUgaG91c2luZyBtYXJrZXQgaW4gQ2hhcmxvdHRlLCBOQy4KCk15IGFuYWx5dGljYWwgYXBwcm9hY2ggc3RhcnQgd2l0aCBsb2FkaW5nIGFuZCBjbGVhbmluZyB0aGUgcHJvdmlkaW5nIGV4aXN0aW5nIGhvdXNpbmcgZGF0YXNldHMuIEkgZmlyc3QgZXhwbG9yZWQgdGhlIGRhdGFzZXRzIGFuZCBpZGVudGlmeSB0aGUgbWlzc2luZyB2YWx1ZXMgYW5kIHBvdGVudGlhbCBvdXRsaWVycy4gVGhlbiwgdXRpbGl6aW5nIHRoZSBmZWF0dXJlIGVuZ2luZWVyaW5nIHByb2Nlc3MsIEkgY3JlYXRlZCBuZXcgZmVhdHVyZXMgZnJvbSB0aGUgZXhpc3RpbmcgZmVhdHVyZXMgdG8gaW1wcm92ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2UsIGluY2x1ZGluZyB0aGUgaG9tZSBhZ2VzIGFuZCB0aGUgZHVtbXkgdmFyaWFibGVzIGZvciB0aGUgYWlyIGNvbmRpdGlvbmluZyBhbmQgaGVhdGluZyBzeXN0ZW0uIEFkZGl0aW9uYWxseSwgc3BhdGlhbCBkYXRhIGFuZCBmZWF0dXJlcyB3ZXJlIGdhdGhlcmVkIGZyb20gdGhlIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXksIGFuZCBvcGVuIGRhdGEgcG9ydGFsLCBpbmNsdWRpbmcgY3JpbWVzIGRhdGEsIHByb3hpbWl0eSB0byByZWNyZWF0aW9uYWwgc3BhY2VzLCBhbmQgdHJlZSBjYW5vcHkuIEZvbGxvd2luZyBkYXRhIHByZXBhcmF0aW9uLCBJIHBhcnRpdGlvbmVkIHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc3Vic2V0cyB1c2luZyBhbiA4MC0yMCBzcGxpdCB0byByZWxpYWJseSBhc3Nlc3MgdGhlIG1vZGVsJ3MgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZS4gVGhlIG1vZGVsIHdhcyBkZXZlbG9wZWQgdXNpbmcgT3JkaW5hcnkgTGVhc3QgU3F1YXJlcyAoT0xTKSByZWdyZXNzaW9uIGFuZCB0ZXN0ZWQgYWNyb3NzIHZhcmlvdXMgY29tYmluYXRpb25zIG9mIHZhcmlhYmxlcy4gVGhlIGZpbmFsIG1vZGVsIHdhcyBldmFsdWF0ZWQgdXNpbmcgZGlhZ25vc3RpYyBtZXRyaWNzIChNQUUsIFJNU0UsIFItc3F1YXJlZCkgYW5kIHNwYXRpYWwgcmVzaWR1YWwgYW5hbHlzZXMsIGluY2x1ZGluZyBNb3JhbidzIEkgdGVzdCwgdG8gZGV0ZWN0IHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGluIHJlc2lkdWFscy4gVGhlIGFuYWx5c2lzIGNvbmNsdWRlcyBieSBhZGRyZXNzaW5nIG1vZGVsIGxpbWl0YXRpb25zIGFuZCBwb3RlbnRpYWwgdG8gaW1wcm92ZW1lbnRzLgoKIyBEYXRhIENvbGxlY3Rpb24gJiBFeHBsb3JpbmcKSW4gdGhpcyBzZWN0aW9uLCBJIGNsZWFuIHRoZSBleGlzdGluZyBob3VzaW5nIGRhdGFzZXRzIGFuZCBleHBsb3JlIG90aGVyIHNvdXJjZXMgb2YgZGF0YSB0aGF0IGFyZSBpbXBvcnRhbnQgZm9yIHRoZSBtb2RlbCBidWlsZGluZyBwcm9jZXNzLiBTZWNvbmQsIEkgY2hlY2sgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHZhcmlhYmxlcyB0byBpZGVudGlmeSB0aGUgbXVsdGljb2xsaW5lYXJpdHkgYmV0d2VlbiB0aGUgdmFyaWFibGVzLCB0aGUgbGluZWFyaXR5IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGtleSBwcmVkaWN0b3JzIGFuZCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLCBhbmQgdGhlIHZpc3VhbCBzZW5zZSBvZiBhdXRvY29ycmVsYXRpb24gaW4gdGhlIGRhdGEuCgojIyBTdGVwIDE6IExvYWQgYW5kIENsZWFuIHRoZSBEYXRhClRoZSBmaXJzdCBzdGVwIGZvciB0aGlzIGFuYWx5c2lzIHN0YXJ0IGJ5IGxvYWRpbmcgdGhlIGhvdXNlaG9sZCBkYXRhIG9mIHRoZSBDaGFybG90dGUsIE5DIGFyZWEuIFRoZSBkYXRhIGlzIGluIHRoZSBnZW9qc29uIGZvcm1hdCB3aXRoIG92ZXIgNDYsMDAwIHJlY29yZHMgb2YgdGhlIGhvdXNpbmcgaW5mb3JtYXRpb24gaW4gTWVja2xlbmJ1cmcgQ291bnR5LCBOQy4KCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KaG91c2U8LSBzdF9yZWFkKCJkYXRhL3N0dWRlbnREYXRhLmdlb2pzb24iKQpgYGAKClRoZSBvcmlnaW5hbCBkYXRhc2V0IGNvbnRhaW4gbWFueSBpbmZvcm1hdGlvbiB0aGF0IGFyZSBub3QgY2xvc2VseSByZWxhdGVkIHdpdGggdGhlIGhvdXNpbmcgcHJpY2VzLCBzdWNoIGFzIHppcCBjb2RlLCBvd25lcidzIG5hbWUsIHRheCBjb2RlLiBJIGZpbHRlcmVkIHRoZSBkYXRhc2V0cyB0aGF0IG9ubHkgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHByaWNlLCBiZWRyb29tcywgeWVhcmJ1aWx0LCBmdWxsYmF0aHMsIGhhbGZiYXRocywgaGVhdGVkYXJlYSwgc3RvcnloZWlnaCwgbnVtZmlyZXBsYSwgYmxkZ2dyYWRlLCB1bml0cywgaGVhdGVkZnVlbCxhbmQgYWN0eXBlIG9mIHRoZSBob3VzZXMuIFRoZSBkYXRhIGlzIGxvYWRlZCBhbmQgY2xlYW5lZCBieSByZW1vdmluZyB0aGUgbWlzc2luZyB2YWx1ZXMgYW5kIHRoZSBob3VzZXMgd2l0aCBwcmljZSBsZXNzIHRoYW4gMC4gRm9yIHByZWRpY3Rpb24gcHVycG9zZXMsIHRoZSBwcmljZSBlcXVhbCBvciBiZWxvdyAwIGlzIG5vdCB1c2VmdWwuCgpgYGB7cn0KaG91c2U8LSBob3VzZSAlPiUKICBkcGx5cjo6c2VsZWN0KHByaWNlLCBiZWRyb29tcyx5ZWFyYnVpbHQsZnVsbGJhdGhzLCBoYWxmYmF0aHMsIGhlYXRlZGFyZWEsIHN0b3J5aGVpZ2gsIG51bWZpcmVwbGEsIGJsZGdncmFkZSx1bml0cywgaGVhdGVkZnVlbCwgYWN0eXBlKSAgJT4lCiAgZmlsdGVyKCFpcy5uYShwcmljZSkgJiAhaXMubmEoYmVkcm9vbXMpICYgIWlzLm5hKHllYXJidWlsdCkgJiAhaXMubmEoZnVsbGJhdGhzKSAmICFpcy5uYShoYWxmYmF0aHMpICYgIWlzLm5hKGhlYXRlZGFyZWEpICYgIWlzLm5hKHN0b3J5aGVpZ2gpICYgIWlzLm5hKG51bWZpcmVwbGEpICYgIWlzLm5hKGJsZGdncmFkZSkgJiAhaXMubmEodW5pdHMpICYgIWlzLm5hKGhlYXRlZGZ1ZWwpICYgIWlzLm5hKGFjdHlwZSkpJT4lCiAgZmlsdGVyKHByaWNlPjApCmBgYAoKQWZ0ZXIgY2hlY2tpbmcgaG91c2luZyBwcmljZXMsIGEgc21hbGwgbnVtYmVyIG9mIGhvbWVzIGluIENoYXJsb3R0ZSBoYXZlIGV4Y2VwdGlvbmFsbHkgaGlnaCB2YWx1ZXMsIHJlcHJlc2VudGluZyBjbGVhciBvdXRsaWVycy4gVGhlc2UgZXh0cmVtZSB2YWx1ZXMgY291bGQgc2lnbmlmaWNhbnRseSBkaXN0b3J0IG91ciBtb2RlbCdzIGFiaWxpdHkgdG8gYWNjdXJhdGVseSBwcmVkaWN0IHR5cGljYWwgaG91c2luZyBwcmljZXMuIFRoZXJlZm9yZSwgSSBoYXZlIGV4Y2x1ZGVkIGFsbCBob21lcyB2YWx1ZWQgYWJvdmUgMTAgbWlsbGlvbiBkb2xsYXJzIHRvIGVuc3VyZSB0aGUgcm9idXN0bmVzcyBhbmQgYWNjdXJhY3kgb2Ygb3VyIHByZWRpY3RpdmUgbW9kZWwuCgpgYGB7cn0KaG91c2U8LSBob3VzZSAlPiUKICBmaWx0ZXIocHJpY2U8MTAwMDAwMDApCmBgYAoKQWZ0ZXIgdGhlIHByaW1hcnkgZmlsdGVyaW5nLCB0aGUgZGF0YXNldCBjb250YWlucyBhYm91dCAzNSwwMDAgcmVjb3JkcyBvZiBob3VzaW5nIGluZm9ybWF0aW9uIGluIE1lY2tsZW5idXJnIENvdW50eSwgTkMuCgojIyMgQ2hlY2sgdGhlIHNrZXduZXNzIG9mIHRoZSB2YXJpYWJsZXMKCkZvciB0aGUgcHJlZGljdGlvbiBwdXJwb3NlLCB0aGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdmFyaWFibGUgbWF0dGVycyBiZWNhdXNlIGl0IGhlbHBzIGVuc3VyZSB0aGUgYWNjdXJhY3kgYW5kIHJlbGlhYmlsaXR5IG9mIHRoZSBtb2RlbC4gV2hlbiB0aGUgdmFyaWFibGUgaXMgbm9ybWFsbHkgZGlzdHJpYnV0ZWQsIGl0IHVzdWFsbHkgbWVhbnMgdGhhdCB0aGUgZXJyb2VzIG9yIHJlc2lkdWFscyBhcmUgYWxzbyBub3JtYWxseSBkaXN0cmlidXRlZC4KClRoZSBoaXN0b2dyYW0gb2YgdGhlIHByaWNlIHNob3dzIHRoYXQgdGhlIHByaWNlIGlzIHJpZ2h0IHNrZXdlZC4gVG8gbWFrZSB0aGUgcHJpY2UgbW9yZSBub3JtYWxseSBkaXN0cmlidXRlZCwgSSB0b29rIHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gb2YgdGhlIHByaWNlLiBUaGUgaGlzdG9ncmFtIG9mIHRoZSBsb2cgcHJpY2Ugc2hvd3MgdGhhdCB0aGUgbG9nIHByaWNlIGlzIG1vcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgdGhhbiB0aGUgb3JpZ2luYWwgcHJpY2UuIEkgdXNlZCB0aGUgbG9nIHByaWNlIGFzIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaW4gdGhlIG1vZGVsIGJ1aWxkaW5nIHByb2Nlc3MuCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KZ2dwbG90KGhvdXNlLCBhZXMoeCA9IHByaWNlKSkgKwogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAibGlnaHRibHVlIiwgYmlucz0gNTAwKSArCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW0gb2YgUHJpY2UiLCB4ID0gIlByaWNlIiwgeSA9ICJGcmVxdWVuY3kiKQpgYGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CmhvdXNlIDwtIGhvdXNlICU+JQogIG11dGF0ZShsb2dwcmljZSA9IGxvZyhwcmljZSkpCgpnZ3Bsb3QoaG91c2UsIGFlcyh4ID0gbG9ncHJpY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJsaWdodGJsdWUiLCBiaW5zPSA1MDApICsKICBsYWJzKHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBMb2cgUHJpY2UiLCB4ID0gIkxvZyBQcmljZSIsIHkgPSAiRnJlcXVlbmN5IikKCmBgYAoKIyMgU3RlcCAyOiBGZWF0dXJlIGVuZ2luZWVyaW5nCgpUaGUgZmVhdHVyZSBlbmdpbmVlcmluZyBpcyB0aGUgcHJvY2VzcyBvZiBjcmVhdGluZyBuZXcgZmVhdHVyZXMgZnJvbSB0aGUgZXhpc3RpbmcgZmVhdHVyZXMuICBJbiB0aGlzIGFuYWx5c2lzLCBJIGNyZWF0ZWQgdGhlIGFnZSBvZiB0aGUgaG91c2UsIGZyb20gdGhlIGhvdXNpbmcgYnVpbHQgeWVhci4gSSBhbHNvIGNyZWF0ZWQgdGhlIGR1bW15IHZhcmlhYmxlcyBmb3IgdGhlIGFpciBjb25kaXRpb25pbmcgYW5kIGhlYXRpbmcgc3lzdGVtLCBhbmQgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGhvdXNpbmcgYHR5cGVgLiBUaGUgZHVtbXkgdmFyaWFibGVzIGZvciB0aGUgYWlyIGNvbmRpdGlvbmluZyBhbmQgaGVhdGluZyBzeXN0ZW0gYXJlIGNyZWF0ZWQgYnkgY2hlY2tpbmcgaWYgdGhlIGhvdXNlIGhhcyBhaXIgY29uZGl0aW9uaW5nIG9yIGhlYXRpbmcgc3lzdGVtLCBhcyBob3VzaW5nIHdpdGggYWlyIGNvbmRpdGlvbmluZyBhbmQgaGVhdGluZyBzeXN0ZW0gdGVuZHMgdG8gaGF2ZSBoaWdoZXIgcHJpY2UuIFRoZSB0eXBlIHZhcmlhYmxlIGlzIGNyZWF0ZWQgYnkgY2F0ZWdvcml6aW5nIHRoZSBob3VzZSBhcyBtYW5zaW9uLCBjYXN0bGUgYW5kIG5vcm1hbCBob3VzZS4gVGhlIGhvdXNlIGhhcyBtb3JlIHRoYW4gNTAwMCBzcXVhcmUgZmVldCBvZiBoZWF0ZWQgYXJlYSwgbW9yZSB0aGFuIDMgYmVkcm9vbXMsIGFuZCBtb3JlIHRoYW4gMyBmdWxsIGJhdGhzIGlhIGNhdGVnb3JpemVkIGFzIG1hbnNpb24uIFRoZSBob3VzZSBoYXMgbW9yZSB0aGFuIDEwMDAwIHNxdWFyZSBmZWV0IG9mIGhlYXRlZCBhcmVhLCBtb3JlIHRoYW4gNSBiZWRyb29tcywgYW5kIG1vcmUgdGhhbiA1IGZ1bGwgYmF0aHMgaXMgY2F0ZWdvcml6ZWQgYXMgY2FzdGxlLiBUaGUgcmVzdCBvZiB0aGUgaG91c2VzIGFyZSBjYXRlZ29yaXplZCBhcyBub3JtYWwgaG91c2UuCgpUaGUgYnVpbGRpbmcgZ3JhZGUgaXMgY29udmVydGVkIHRvIGEgbnVtZXJpYyB2YXJpYWJsZSBieSBhc3NpZ25pbmcgYSBudW1iZXIgdG8gZWFjaCBjYXRlZ29yeSBiYXNlZCBvbiBmYWN0b3JzLiBUaGUgbmV3IGZlYXR1cmVzIGFyZSBjcmVhdGVkIHRvIGltcHJvdmUgdGhlIG1vZGVsIHBlcmZvcm1hbmNlLgoKCnwgR3JhZGUgICAgICAgIHwgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVHlwaWNhbCBFeGFtcGxlcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBRdWFsaXR5ICYgVmFsdWUgTGV2ZWwgICAgIHwKfC0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8ICoqTWluaW11bSoqICB8IFZlcnkgYmFzaWMsIG1pbmltYWwgc3RhbmRhcmRzLGxpbWl0ZWQgZHVyYWJpbGl0eS4gfCBPbGQgc2hlZHMsIHNpbXBsZSBvdXRidWlsZGluZ3MsIGRldGVyaW9yYXRpbmcgc3RydWN0dXJlcyB8ICoqTG93ZXN0KiogICAgICAgICAgICAgICAgfAp8ICoqRmFpcioqICAgICB8IEJlbG93LWF2ZXJhZ2UgY29uZGl0aW9uLCBub3RpY2VhYmxlIHdlYXIgb3IgZGVmZXJyZWQgbWFpbnRlbmFuY2UuIHwgT2xkZXIgaG9tZXMvYXBhcnRtZW50cyB3aXRoIGRlZmVycmVkIG1haW50ZW5hbmNlLCBzdHJ1Y3R1cmVzIG5lZWRpbmcgcmVub3ZhdGlvbiB8ICoqQmVsb3cgQXZlcmFnZSoqICAgICAgICAgfAp8ICoqQXZlcmFnZSoqICB8IFN0YW5kYXJkIGNvbnN0cnVjdGlvbiBxdWFsaXR5LCBmdW5jdGlvbmFsIGFuZCBtYWludGFpbmVkIGNvbmRpdGlvbi4gfCBUeXBpY2FsIHNpbmdsZS1mYW1pbHkgaG9tZXMsIHN0YW5kYXJkIGFwYXJ0bWVudCB1bml0cyB8ICoqTW9kZXJhdGUqKiAgICAgICAgICAgICAgfAp8ICoqR29vZCoqICAgICB8IEFib3ZlLWF2ZXJhZ2UgcXVhbGl0eSBjb25zdHJ1Y3Rpb24sIGdvb2QgY29uZGl0aW9uLiB8IE5ld2VyIHN1YnVyYmFuIGhvbWVzLCByZW5vdmF0ZWQgdW5pdHMsIGhpZ2hlci1xdWFsaXR5IGNvbW1lcmNpYWwgYnVpbGRpbmdzIHwgKipBYm92ZSBBdmVyYWdlKiogICAgICAgICB8CnwgKipFeGNlbGxlbnQqKnwgU3VwZXJpb3IgcXVhbGl0eSwgZXhjZXB0aW9uYWwgZmluaXNoZXMsIGFuZCBtZXRpY3Vsb3VzIGF0dGVudGlvbiB0byBkZXRhaWwuIHwgSGlnaC1lbmQgcmVzaWRlbmNlcywgbHV4dXJ5IGNvbmRvbWluaXVtcywgdXBzY2FsZSBjb21tZXJjaWFsIHByb3BlcnRpZXMgfCAqKkhpZ2gqKiAgICAgICAgICAgICAgICAgIHwKfCAqKkN1c3RvbSoqICAgfCBVbmlxdWUgb3Igc3BlY2lhbGl6ZWQgY29uc3RydWN0aW9uLCBoaWdobHkgcGVyc29uYWxpemVkIGRlc2lnbix0eXBpY2FsbHkgbHV4dXJ5IG9yIGFyY2hpdGVjdHVyYWxseSBzaWduaWZpY2FudC4gfCBMdXh1cnkgY3VzdG9tIGhvbWVzLCB1bmlxdWUgaGlzdG9yaWNhbCByZXN0b3JhdGlvbnMsIGFyY2hpdGVjdC1kZXNpZ25lZCBoaWdoLWVuZCBwcm9wZXJ0aWVzIHwgKipIaWdoZXN0IG9yIFNwZWNpYWxpemVkKiogfAoKU291cmNlOiBodHRwczovL2xvY2FsZG9jcy5jaGFybG90dGUuZWR1L1RheF9Db2xsZWN0aW9ucy9SZXBvcnRzX1N0dWRpZXMvCgoKYGBge3J9CmhvdXNlIDwtIGhvdXNlICU+JQogIG11dGF0ZSgKICAgIGFnZSA9IDIwMjIgLSB5ZWFyYnVpbHQsCiAgICBzdG9yeWhlaWdoID0gYXMubnVtZXJpYyhnc3ViKCJbXjAtOS5dIiwgIiIsIHN0b3J5aGVpZ2gpKSwKICAgIGJsZGdncmFkZSA9IGNhc2Vfd2hlbigKICAgICAgYmxkZ2dyYWRlID09ICJNSU5JTVVNIiB+IDEsCiAgICAgIGJsZGdncmFkZSA9PSAiRkFJUiIgfiAyLAogICAgICBibGRnZ3JhZGUgPT0gIkFWRVJBR0UiIH4gMywKICAgICAgYmxkZ2dyYWRlID09ICJHT09EIiB+IDQsCiAgICAgIGJsZGdncmFkZSA9PSAiVkVSWSBHT09EIiB+IDUsCiAgICAgIGJsZGdncmFkZSA9PSAiRVhDRUxMRU5UIiB+IDYsCiAgICAgIGJsZGdncmFkZSA9PSAiQ1VTVE9NIiB+IDcpLAogICAgYWNfZHVtbXkgPSBpZmVsc2UoYWN0eXBlID09ICJBQy1OT05FIiwgMCwgMSksCiAgICBoZWF0X2R1bW15ID0gaWZlbHNlKGhlYXRlZGZ1ZWwgPT0gIk5PTkUiLCAwLCAxKSwKICAgIG1hbnNpb249IGlmZWxzZShoZWF0ZWRhcmVhPjUwMDAgJiBoZWF0ZWRhcmVhPDEwMDAxICYgYmVkcm9vbXM+MiAmIGJlZHJvb21zPDYgJiBmdWxsYmF0aHM+MiwgMSwgMCksCiAgICBjYXN0bGU9IGlmZWxzZShoZWF0ZWRhcmVhPjEwMDAwICYgYmVkcm9vbXM+NSAmIGZ1bGxiYXRocz41LCAxLCAwKSwKICAgIGhvdXNlPSBpZmVsc2UobWFuc2lvbj09MCAmIGNhc3RsZT09MCwgMSwgMCksCiAgICBoZWF0ZnVlbCA9IGlmZWxzZShoZWF0ZWRmdWVsID09ICJOT05FIiwgTkEsIGhlYXRlZGZ1ZWwpCiAgICApCgpob3VzZSA8LSBob3VzZSAlPiUKICBkcGx5cjo6c2VsZWN0KC15ZWFyYnVpbHQsIC1hY3R5cGUsIC1oZWF0ZWRmdWVsKSU+JQogIG11dGF0ZSh0eXBlPSBjYXNlX3doZW4oCiAgICBob3VzZT09MSB+ICJIb3VzZSIsCiAgICBtYW5zaW9uPT0xIH4gIk1hbnNpb24iLAogICAgY2FzdGxlPT0xIH4gIkNhc3RsZSIKICApKQpgYGAKCgojIyMgQWRkaXRpb25hbCBzcGF0aWFsIGRhdGEgdG8gcHJlZGljdCB0aGUgaG91c2luZyBwcmljZQoKTWFueSBvdGhlciBmYWN0b3JzIGNhbiBhZmZlY3QgdGhlIGhvdXNpbmcgcHJpY2UgdGhhdCBub3QgY29udGFpbnMgaW4gdGhlIGdpdmVuIGRhdGFzZXRzLiBNYW55IG90aGVyIHNwYXRpYWwgdmFyaWFibGVzIGFmZmVjdCB0aGUgaG91c2luZyBwcmljZSBhbmQgdmFsdWUgYXMgd2VsbC4gRm9yIGV4YW1wbGUsIHRoZSBob3VzaW5nIHZhbHVlcyBhcmUgY2xvc2VseSBjb3JyZWxhdGUgd2l0aCB0aGUgY3JpbWUgcmF0ZXMgaW4gdGhlIG5laWdoYm9yaG9vZCwgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUgYW5kIHdoZXRoZXIgdGhlIGhvdXNlIGlzIGNsb3NlIHRvIHRoZSBwdWJsaWMgdHJhbnNwb3J0YXRpb24gYW5kIGFjY2VzcyB0byB0aGUgcGFyayBhbmQgcHVibGljIHNwYWNlcy4gQWxsIHRob3NlIHZhcmlhYmxlcyBzaG91bGQgYmUgaW5jbHVkZWQgaW4gdGhlIHByZWRpY3Rpb24gbW9kZWwgdG8gaW1wcm92ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2UuCgpUaGUgYWRkaXRpb25hbCBzcGF0aWFsIGRhdGEgaXMgY29sbGVjdGVkIGZyb20gQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSwgQ2l0eSBvZiBDaGFybG90dGUgb3BlbiBkYXRhIHBvcnRhbCBhbmQgb3RoZXIgc291cmNlcy4gVGhlIGFkZGl0aW9uYWwgc3BhdGlhbCBkYXRhIGluY2x1ZGVzIHRoZSBjcmltZSByYXRlLCB0aGUgZGlzdGFuY2UgdG8gdGhlIHB1YmxpYyB0cmFuc3BvcnRhdGlvbiwgdGhlIGRpc3RhbmNlIHRvIHRoZSBwYXJrLiBUaGUgYWRkaXRpb25hbCBzcGF0aWFsIGRhdGEgaXMgY29sbGVjdGVkIGFuZCBjbGVhbmVkIHRvIGJlIHVzZWQgaW4gdGhlIG1vZGVsIGJ1aWxkaW5nIHByb2Nlc3MuCgoqKk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIGRhdGEqKiB3YXMgY29sbGVjdGVkIGZyb20gdGhlIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgYXQgdGhlIGNlbnN1cyBibG9jayBncm91cCBsZXZlbC4gVGhlIG1lZGlhbiBob3VzZWhvbGQgaW5jb21lIHZhbHVlcyB3ZXJlIHRoZW4gYXNzaWduZWQgdG8gaW5kaXZpZHVhbCBob3VzaW5nIGRhdGEgcG9pbnRzIGJhc2VkIG9uIHRoZWlyIGdlb2dyYXBoaWMgbG9jYXRpb24uCgpgYGB7ciwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmluY29tZTwtIGdldF9hY3MoCiAgZ2VvZ3JhcGh5ID0gImJsb2NrIGdyb3VwIiwKICB2YXJpYWJsZXMgPSBjKCJpbmNvbWUiPSJCMTkwMTNfMDAxIiksCiAgc3RhdGUgPSAiTkMiLAogIGNvdW50eSA9ICJNZWNrbGVuYnVyZyIsCiAgZ2VvbWV0cnkgPSBUUlVFCiAgKSU+JQogIHN0X3RyYW5zZm9ybShjcnMgPXN0X2Nycyhob3VzZSkpJT4lCiAgZmlsdGVyKCFpcy5uYShlc3RpbWF0ZSkpJT4lCiAgZHBseXI6OnNlbGVjdChlc3RpbWF0ZSwgR0VPSUQsIGdlb21ldHJ5KSU+JQogIHJlbmFtZShpbmNvbWU9ZXN0aW1hdGUpCgojc3BhdGlhbCBqb2luIHRoZSBpbmNvbWUgZGF0YSB0byB0aGUgaG91c2UgZGF0YQpob3VzZSA8LSBzdF9qb2luKGhvdXNlLCBpbmNvbWUpJT4lCiAgZmlsdGVyKCFpcy5uYShpbmNvbWUpKQpgYGAKCioqQ3JpbWUgZGF0YSoqIHdhcyBjb2xsZWN0ZWQgZnJvbSB0aGUgQ2l0eSBvZiBDaGFybG90dGUgb3BlbiBkYXRhIHBvcnRhbCBmcm9tIHBvbGljZSBzdGF0aW9uLiBBbmQgdGhlbiwgdGhlIGNyaW1lIGRhdGEgaXMgYWdncmVnYXRlZCBhdCB0aGUgY2Vuc3VzIGJsb2NrIGdyb3VwIGxldmVsIHNhbWUgYXMgdGhlIGhvdXNlaG9sZCBpbmNvbWUgZGF0YS4gRmluYWxseSwgdGhlIGNyaW1lIGRhdGEgd2FzIHNwYXRpYWxseSBqb2luZWQgdG8gdGhlIGhvdXNlIGRhdGEgYmFzZWQgb24gdGhlIGhvdXNlIGdlb2xvY2F0aW9uLgoKYGBge3J9CmNyaW1lPC0gcmVhZC5jc3YoImRhdGEvQ01QRC5jc3YiKSU+JQogIGZpbHRlcihZRUFSPj0yMDIxKQoKY3JpbWU8LSBjcmltZSAlPiUKICBkcGx5cjo6c2VsZWN0KExBVElUVURFX1BVQkxJQywgTE9OR0lUVURFX1BVQkxJQywgWUVBUiklPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJMT05HSVRVREVfUFVCTElDIiwgIkxBVElUVURFX1BVQkxJQyIpLCBjcnMgPSA0MzI2KSU+JQogIHN0X3RyYW5zZm9ybShjcmltZSwgY3JzID1zdF9jcnMoaG91c2UpKQoKY3JpbWVfdHJhY3Q8LSBzdF9qb2luKGhvdXNlLCBjcmltZSklPiUKICBncm91cF9ieShHRU9JRCklPiUKICBzdW1tYXJpc2UoY3JpbWU9bigpKQoKaG91c2UgPC0gc3Rfam9pbihob3VzZSwgY3JpbWVfdHJhY3QpJT4lCiAgZHBseXI6OnNlbGVjdCgtR0VPSUQueCwgLUdFT0lELnkpJT4lCiAgZmlsdGVyKCFpcy5uYShjcmltZSkpCmBgYAoKKipQdWJsaWMgdHJhbnNwb3J0YXRpb24gY292ZXJhZ2UgZGF0YSoqIHdhcyBjb2xsZWN0ZWQgZnJvbSB0aGUgQ2hhcmxvdHRlL01lY2tsZW5idXJnIFF1YWxpdHkgb2YgTGlmZSBFeHBsb3Jlci4gVGhpcyBkYXRhc2V0IG1lYXN1cmVzIHRoZSBwZXJjZW50YWdlIG9mIGhvdXNpbmcgdW5pdHMgbG9jYXRlZCB3aXRoaW4gMC41IG1pbGVzIG9mIGEgdHJhbnNpdCBzdG9wIGF0IHRoZSBuZWlnaGJvcmhvb2QgbGV2ZWwgYXMgZGVmaW5lZCBieSBNZWNrbGVuYnVyZyBDb3VudHkuIFRoZSBuZWlnaGJvcmhvb2QtbGV2ZWwgdHJhbnNpdCBjb3ZlcmFnZSB2YWx1ZXMgd2VyZSB0aGVuIGFzc2lnbmVkIHRvIGluZGl2aWR1YWwgaG91c2luZyBkYXRhIHBvaW50cyBiYXNlZCBvbiB0aGVpciBnZW9ncmFwaGljIGxvY2F0aW9uLgoKYGBge3IsIHJlc3VsdHM9J2hpZGUnfQp0cmFuc2l0IDwtIHN0X3JlYWQoImRhdGEvdHJhbnNwb3J0YXRpb24uZ2VvanNvbiIpJT4lCiAgZHBseXI6OnNlbGVjdChYMjAyMywgZ2VvbWV0cnkpJT4lCiAgcmVuYW1lKHRyYW5zaXQ9WDIwMjMpCgojc3BhdGlhbCBqb2luIHRoZSB0cmFuc2l0IGRhdGEgdG8gdGhlIGhvdXNlIGRhdGEKaG91c2UgPC0gc3Rfam9pbihob3VzZSwgdHJhbnNpdCklPiUKICBmaWx0ZXIoIWlzLm5hKHRyYW5zaXQpKQpgYGAKCioqVHJlZSBDYW5vcHkgZGF0YSoqIHdhcyBjb2xsZWN0ZWQgZnJvbSB0aGUgQ2hhcmxvdHRlL01lY2tsZW5idXJnIFF1YWxpdHkgb2YgTGlmZSBFeHBsb3Jlci4gVGhpcyBkYXRhc2V0IHJlcHJlc2VudHMgdGhlIHBlcmNlbnRhZ2Ugb2YgdHJlZSBjb3ZlciBpbiBlYWNoIG5laWdoYm9yaG9vZC4gTGlrZSB0aGUgdHJhbnNpdCBjb3ZlcmFnZSBkYXRhLCB0aGVzZSB2YWx1ZXMgYXJlIGxpbmtlZCB0byB0aGUgaG91c2luZyBkYXRhIGJhc2VkIG9uIGVhY2ggcHJvcGVydHkncyBnZW9sb2NhdGlvbi4KCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KdHJlZSA8LSBzdF9yZWFkKCJkYXRhL3RyZWVfY2Fub3B5Lmdlb2pzb24iKSU+JQogIGRwbHlyOjpzZWxlY3QoWDIwMjMsIGdlb21ldHJ5KSU+JQogIHJlbmFtZShjYW5vcHk9WDIwMjMpCgojc3BhdGlhbCBqb2luIHRoZSB0cmVlIGNhbm9weSBkYXRhIHRvIHRoZSBob3VzZSBkYXRhCmhvdXNlIDwtIHN0X2pvaW4oaG91c2UsIHRyZWUpCmBgYAoKKipBcHByb3hpbWF0ZSBkaXN0YW5jZSB0byB0aGUgcGFyayBkYXRhKiogd2FzIGNvbGxlY3RlZCBmcm9tIHRoZSBDaGFybG90dGUvTWVja2xlbmJ1cmcgUXVhbGl0eSBvZiBMaWZlIEV4cGxvcmVyLiBUaGlzIGRhdGFzZXQgbWVhc3VyZXMgdGhlIHBlcmNlbnRhZ2Ugb2YgaG91c2luZyB1bml0cyB3aXRoaW4gMC41IG1pbGVzIG9mIGFuIG91dGRvb3IgcHVibGljIHJlY3JlYXRpb24gYXJlYSBhdCB0aGUgbmVpZ2hib3Job29kIGxldmVsLiBTaW1pbGFyIHRvIG90aGVyIHNwYXRpYWwgZGF0YXNldHMsIHRoZSBwYXJrIGFjY2Vzc2liaWxpdHkgZGF0YSB3YXMgYXNzaWduZWQgdG8gaW5kaXZpZHVhbCBob3VzaW5nIHJlY29yZHMgYmFzZWQgb24gdGhlaXIgZ2VvZ3JhcGhpYyBsb2NhdGlvbnMuCgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9CnJlY3JlYXRpb248LSBzdF9yZWFkKCJkYXRhL3JlY3JlYXRpb24uZ2VvanNvbiIpJT4lCiAgZHBseXI6OnNlbGVjdChYMjAyMywgZ2VvbWV0cnkpJT4lCiAgcmVuYW1lKHJlY3JlYXRpb249WDIwMjMpCgojc3BhdGlhbCBqb2luIHRoZSByZWNyZWF0aW9uIGRhdGEgdG8gdGhlIGhvdXNlIGRhdGEKaG91c2UgPC0gc3Rfam9pbihob3VzZSwgcmVjcmVhdGlvbiklPiUKICBmaWx0ZXIoIWlzLm5hKHJlY3JlYXRpb24pKQpgYGAKVGhlIHBvdGVudGlhbCBkcmF3YmFjayBvZiB0aGVzZSBhcHByb2FjaGVzIGlzIHRoYXQgdGhlIGRhdGEgYXJlIGFnZ3JlZ2F0ZWQgYXQgbGFyZ2UgZ2VvZ3JhcGhpYyB1bml0cyByYXRoZXIgdGhhbiBhdCB0aGUgaW5kaXZpZHVhbCBob3VzZWhvbGQgbGV2ZWwuIEhvdXNlaG9sZHMgd2l0aGluIHRoZSBzYW1lIGNlbnN1cyBibG9jayBncm91cCBvciBuZWlnaGJvcmhvb2QgbWF5IGRpZmZlciBpbiB0aGVpciBhY2Nlc3MgdG8gdHJhbnNpdCBhbmQgb3RoZXIgc2VydmljZXMuIEFkZGl0aW9uYWxseSwgaG91c2Vob2xkcyB3aXRoaW4gdGhlIHNhbWUgbmVpZ2hib3Job29kIG1heSB2YXJ5IHNpZ25pZmljYW50bHkgaW4gaW5jb21lIGxldmVscy4gRGVzcGl0ZSB0aGVzZSBsaW1pdGF0aW9ucywgdGhlIGFnZ3JlZ2F0ZWQgZGF0YSByZXByZXNlbnQgdGhlIGJlc3QgYXZhaWxhYmxlIHNvdXJjZSBmb3IgdXNlIGluIHRoZSBtb2RlbC1idWlsZGluZyBwcm9jZXNzLgoKIyMgU3RlcCAzOiBTdW1tYXJ5IFN0YXRpc3RpY3MgYW5kIENvcnJlbGF0aW9uIE1hdHJpeAoKVGhlIHN1bW1hcnkgc3RhdGlzdGljcyBhbmQgY29ycmVsYXRpb24gbWF0cml4IGFyZSBjYWxjdWxhdGVkIHRvIHVuZGVyc3RhbmQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuIFRoZSBzdW1tYXJ5IHN0YXRpc3RpY3Mgc2hvdyB0aGUgbWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIChTRCkgb2YgdGhlIHZhcmlhYmxlcy4gVGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBzaG93cyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdmFyaWFibGVzLiBUaGUgY29ycmVsYXRpb24gbWF0cml4IGlzIHVzZWQgdG8gdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHZhcmlhYmxlcyBhbmQgdG8gaWRlbnRpZnkgdGhlIG11bHRpY29sbGluZWFyaXR5IGJldHdlZW4gdGhlIHZhcmlhYmxlcy4gTGFzdGx5LCB2YXJpb3VzIHNjYXR0ZXIgcGxvdHMgYXJlIGNyZWF0ZWQgdG8gdmlzdWFsaXplIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUga2V5IHByZWRpY3RvcnMgYW5kIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgdG8gZW5zdXJlIHRoZSBsaW5lYXJpdHkgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHByZWRpY3RvcnMgYW5kIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUuCgojIyMgU3VtbWFyeSBTdGF0aXN0aWNzCkZvciB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzLCBJIGNhbGN1bGF0ZWQgdGhlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgdmFyaWFibGVzLiBUaGUgc3VtbWFyeSBzdGF0aXN0aWNzIGFyZSBjYWxjdWxhdGVkIHRvIHVuZGVyc3RhbmQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdmFyaWFibGVzLiBUaGUgc3VtbWFyeSBzdGF0aXN0aWNzIGFyZSB1c2VkIHRvIHVuZGVyc3RhbmQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdmFyaWFibGVzIGFuZCB0byBpZGVudGlmeSB0aGUgb3V0bGllcnMgaW4gdGhlIGRhdGEuCgpgYGB7ciBjaGFydH0KZGVwZW5kZW50X3ZhciA8LSAibG9ncHJpY2UiCgpwcmVkaWN0b3JzIDwtIGMoImJlZHJvb21zIiwgImZ1bGxiYXRocyIsICJoYWxmYmF0aHMiLCAiaGVhdGVkYXJlYSIsICJzdG9yeWhlaWdoIiwgIm51bWZpcmVwbGEiLCAiYmxkZ2dyYWRlIiwgInVuaXRzIiwgImFnZSIsICJpbmNvbWUiLCAiY3JpbWUiLCAidHJhbnNpdCIsICJjYW5vcHkiLCAicmVjcmVhdGlvbiIpCgpzdW1tYXJ5X3N0YXRzIDwtIGhvdXNlICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBkcGx5cjo6c2VsZWN0KGFsbF9vZihjKGRlcGVuZGVudF92YXIsIHByZWRpY3RvcnMpKSkgJT4lCiAgc3VtbWFyaXNlX2FsbChsaXN0KE1lYW4gPSBtZWFuLCBTRCA9IHNkKSwgbmEucm0gPSBUUlVFKSAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAiVmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAiVmFsdWUiKSAlPiUKICBzZXBhcmF0ZShWYXJpYWJsZSwgaW50byA9IGMoIlZhcmlhYmxlIiwgIlN0YXQiKSwgc2VwID0gIl8iKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gU3RhdCwgdmFsdWVzX2Zyb20gPSBWYWx1ZSkKCgoKc3VtbWFyeV9zdGF0cyA8LSBzdW1tYXJ5X3N0YXRzICU+JQogIG11dGF0ZShWYXJpYWJsZSA9IGNhc2Vfd2hlbigKICAgIFZhcmlhYmxlID09ICJsb2dwcmljZSIgfiAiTG9nIHRyYW5zZm9ybWVkIEhvdXNlIFZhbHVlIiwKICAgIFZhcmlhYmxlID09ICJiZWRyb29tcyIgfiAiTnVtYmVyIG9mIGJlZHJvb21zIiwKICAgIFZhcmlhYmxlID09ICJmdWxsYmF0aHMiIH4gIk51bWJlciBvZiBmdWxsIGJhdGhzIiwKICAgIFZhcmlhYmxlID09ICJoYWxmYmF0aHMiIH4gIk51bWJlciBvZiBoYWxmIGJhdGhzIiwKICAgIFZhcmlhYmxlID09ICJoZWF0ZWRhcmVhIiB+ICJIb3VzZSBTcXVhcmUgZm9vdGFnZSIsCiAgICBWYXJpYWJsZSA9PSAic3RvcnloZWlnaCIgfiAiTnVtYmVyIG9mIHN0b3JpZXMiLAogICAgVmFyaWFibGUgPT0gIm51bWZpcmVwbGEiIH4gIk51bWJlciBvZiBmaXJlcGxhY2VzIiwKICAgIFZhcmlhYmxlID09ICJibGRnZ3JhZGUiIH4gIkJ1aWxkaW5nIEdyYWRlIiwKICAgIFZhcmlhYmxlID09ICJ1bml0cyIgfiAiTnVtYmVyIG9mIHVuaXRzIiwKICAgIFZhcmlhYmxlID09ICJhZ2UiIH4gIkFnZSBvZiB0aGUgaG91c2UiLAogICAgVmFyaWFibGUgPT0gImluY29tZSIgfiAiTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUiLAogICAgVmFyaWFibGUgPT0gImNyaW1lIiB+ICJOdW1iZXIgb2YgQ3JpbWUgaW4gQ2Vuc3VzIEJsb2NrIGdyb3VwIiwKICAgIFZhcmlhYmxlID09ICJ0cmFuc2l0IiB+ICIlIFRyYW5zaXQgQ292ZXJhZ2UiLAogICAgVmFyaWFibGUgPT0gImNhbm9weSIgfiAiJSBUcmVlIENhbm9weSBDb3ZlcmFnZSIsCiAgICBWYXJpYWJsZSA9PSAicmVjcmVhdGlvbiIgfiAiJSBSZWNyZWF0aW9uIENvdmVyYWdlIiwKICAgIFRSVUUgfiBWYXJpYWJsZQogICkpCgoKCgpzdW1tYXJ5X3N0YXRzIDwtIHN1bW1hcnlfc3RhdHMgJT4lCiAgbXV0YXRlKAogICAgTWVhbiA9IHJvdW5kKE1lYW4sIDIpLAogICAgU0QgPSByb3VuZChTRCwgMikKICApCgpzdW1tYXJ5X3N0YXRzIDwtIHN1bW1hcnlfc3RhdHMgJT4lCiAgYXJyYW5nZShWYXJpYWJsZSA9PSAiTG9nIHRyYW5zZm9ybWVkIEhvdXNlIFZhbHVlIikKCnByZWRpY3Rvcl9yb3dzIDwtIHdoaWNoKHN1bW1hcnlfc3RhdHMkVmFyaWFibGUgIT0gIkxvZyB0cmFuc2Zvcm1lZCBIb3VzZSBWYWx1ZSIpCmRlcGVuZGVudF9yb3dzIDwtIHdoaWNoKHN1bW1hcnlfc3RhdHMkVmFyaWFibGUgPT0gIkxvZyB0cmFuc2Zvcm1lZCBIb3VzZSBWYWx1ZSIpCgojIERldGVybWluZSB0aGUgc3RhcnQgYW5kIGVuZCByb3dzIGZvciBlYWNoIGdyb3VwCnN0YXJ0X3ByZWQgPC0gbWluKHByZWRpY3Rvcl9yb3dzKQplbmRfcHJlZCAgIDwtIG1heChwcmVkaWN0b3Jfcm93cykKc3RhcnRfZGVwICA8LSBtaW4oZGVwZW5kZW50X3Jvd3MpCmVuZF9kZXAgICAgPC0gbWF4KGRlcGVuZGVudF9yb3dzKQoKIyBDcmVhdGUgdGhlIHRhYmxlIHVzaW5nIGthYmxlIGFuZCBhZGQgZXh0cmEgZm9ybWF0dGluZwprYWJsZShzdW1tYXJ5X3N0YXRzLCBjYXB0aW9uID0gIlN1bW1hcnkgU3RhdGlzdGljcyIsCiAgICAgIGFsaWduID0gYygibCIsICJsIiwgImwiKSwgYm9va3RhYnMgPSBUUlVFLCBlc2NhcGUgPSBGQUxTRSApICU+JQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICIgPSAxLCAiU3RhdGlzdGljcyIgPSAyKSkgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpICU+JQogIGdyb3VwX3Jvd3MoIlByZWRpY3RvcnMiLCBzdGFydF9wcmVkLCBlbmRfcHJlZCkgJT4lCiAgZ3JvdXBfcm93cygiRGVwZW5kZW50IFZhcmlhYmxlIiwgc3RhcnRfZGVwLCBlbmRfZGVwKSU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLCBmdWxsX3dpZHRoID0gVFJVRSkKYGBgCgojIyMgQ29ycmVsYXRpb24gTWF0cml4CgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KCnByZWRpY3Rvcl92YXJzIDwtIGhvdXNlWywgYygiYmVkcm9vbXMiLCAiZnVsbGJhdGhzIiwgImhhbGZiYXRocyIsICJoZWF0ZWRhcmVhIiwgInN0b3J5aGVpZ2giLCAibnVtZmlyZXBsYSIsICJibGRnZ3JhZGUiLCAidW5pdHMiLCAiYWdlIiwgImFjX2R1bW15IiwgImhlYXRfZHVtbXkiLCAiaW5jb21lIiwgImNyaW1lIiwgInRyYW5zaXQiLCAiY2Fub3B5IiwgInJlY3JlYXRpb24iKV0lPiVzdF9kcm9wX2dlb21ldHJ5KCkKCgpjb3JfbWF0cml4IDwtIGNvcihwcmVkaWN0b3JfdmFycywgdXNlID0gImNvbXBsZXRlLm9icyIsIG1ldGhvZCA9ICJwZWFyc29uIikKCmdnY29ycnBsb3QoY29yX21hdHJpeCwKICAgICAgICAgICBtZXRob2QgPSAic3F1YXJlIiwKICAgICAgICAgICB0eXBlID0gImxvd2VyIiwKICAgICAgICAgICBsYWIgPSBUUlVFLAogICAgICAgICAgIGxhYl9zaXplID0gMywKICAgICAgICAgICBjb2xvcnMgPSBjKCIjYTQxMzNjIiwgIndoaXRlIiwgIiNhNDEzM2MiKSkrCiAgICBsYWJzKHRpdGxlID0gIkNvcnJlbGF0aW9uIE1hdHJpeCBmb3IgYWxsIFByZWRpY3RvciBWYXJpYWJsZXMiKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gNyksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQpgYGAKClNpbmNlIHRoZXJlIGFyZSBoaWdoIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGhlYXRlZCBhcmVhIGFuZCBmdWxsIGJhdGhzLCBJIHdpbGwgcmVtb3ZlIHRoZSBmdWxsIGJhdGhzIGZyb20gdGhlIG1vZGVsIGJ1aWxkaW5nIHByb2Nlc3MuIFRoZSBmdWxsIGJhdGhzIGlzIHJlbW92ZWQgdG8gYXZvaWQgdGhlIG11bHRpY29sbGluZWFyaXR5IGJldHdlZW4gdGhlIHZhcmlhYmxlcy4gU2ltaWxhcmx5LCB0aGUgbnVtYmVyIG9mIGJlZHJvb21zIGFuZCBoZWF0ZWQgYXJlYSBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQsIEkgd2lsbCByZW1vdmUgdGhlIG51bWJlciBvZiBiZWRyb29tIGEgZnJvbSB0aGUgbW9kZWwgYnVpbGRpbmcgcHJvY2Vzcy4gU3RvcnloZWlnaHQgaXMgYWxzbyByZW1vdmVkIGZvciB0aGUgc2FtZSByZWFzb24uCgpBZnRlciByZW1vdmluZywgdGhlIGZpbmFsIGNvcnJlbGF0aW9uIG1hdHJpeCBpcyBmb2xsb3dpbmc6CmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQpwcmVkaWN0b3JfdmFycyA8LSBob3VzZVssIGMoICJoYWxmYmF0aHMiLCAiaGVhdGVkYXJlYSIsICJudW1maXJlcGxhIiwgImJsZGdncmFkZSIsICJ1bml0cyIsICJhZ2UiLCAiYWNfZHVtbXkiLCAiaGVhdF9kdW1teSIsICJpbmNvbWUiLCAiY3JpbWUiLCAidHJhbnNpdCIsICJjYW5vcHkiLCAicmVjcmVhdGlvbiIpXSU+JXN0X2Ryb3BfZ2VvbWV0cnkoKQoKCmNvcl9tYXRyaXggPC0gY29yKHByZWRpY3Rvcl92YXJzLCB1c2UgPSAiY29tcGxldGUub2JzIiwgbWV0aG9kID0gInBlYXJzb24iKQoKZ2djb3JycGxvdChjb3JfbWF0cml4LAogICAgICAgICAgIG1ldGhvZCA9ICJzcXVhcmUiLAogICAgICAgICAgIHR5cGUgPSAibG93ZXIiLAogICAgICAgICAgIGxhYiA9IFRSVUUsCiAgICAgICAgICAgbGFiX3NpemUgPSAzLAogICAgICAgICAgIGNvbG9ycyA9IGMoIiNhNDEzM2MiLCAid2hpdGUiLCAiI2E0MTMzYyIpKSsKICAgIGxhYnModGl0bGUgPSAiQ29ycmVsYXRpb24gTWF0cml4IGZvciBhbGwgUHJlZGljdG9yIFZhcmlhYmxlcyIpICsKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNyksCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCmhvdXNlPC0gaG91c2UgJT4lCiAgZHBseXI6OnNlbGVjdCgtZnVsbGJhdGhzLCAtc3RvcnloZWlnaCwgLWJlZHJvb21zKQoKYGBgCgoqKkNoZWNrIHRoZSBWSUYqKgoKVG8gZW5zdXJlIHRoZSBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMsIEkgY2hlY2tlZCB0aGUgVmFyaWFuY2UgSW5mbGF0aW9uIEZhY3RvciAoVklGKSBvZiB0aGUgdmFyaWFibGVzLiBUaGUgVklGIHZhbHVlcyBhcmUgY2FsY3VsYXRlZCB0byBmdXJ0aGVyIGNoZWNrIHRoZSBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuIFRoZSBWSUYgdmFsdWVzIGFyZSB1c2VkIHRvIGlkZW50aWZ5IHRoZSBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuIFRoZSBWSUYgdmFsdWVzIGFib3ZlIDUgaW5kaWNhdGUgaGlnaCBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuIFRoZSBWSUYgdmFsdWVzIGFyZSBjYWxjdWxhdGVkIHRvIGVuc3VyZSB0aGUgbXVsdGljb2xsaW5lYXJpdHkgYmV0d2VlbiB0aGUgdmFyaWFibGVzLiBUaGUgVklGIHZhbHVlcyBmb3IgYWxsIHRoZSB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsIGFyZSBiZWxvdyAzLCB3aGljaCBpbmRpY2F0ZXMgdGhhdCB0aGVyZSBpcyBubyBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuCgpgYGB7cn0KbW9kZWw8LSBsbShsb2dwcmljZSB+IGhhbGZiYXRocyArIGhlYXRlZGFyZWEgKyBudW1maXJlcGxhICsgYmxkZ2dyYWRlICsgdW5pdHMgKyBhZ2UgKyBhY19kdW1teSArIGhlYXRfZHVtbXkgKyBpbmNvbWUgKyBjcmltZSArIHRyYW5zaXQgKyBjYW5vcHkgKyByZWNyZWF0aW9uLCBkYXRhID0gaG91c2UpCgojIENvbXB1dGUgVklGIHZhbHVlcwp2aWZfdmFsdWVzIDwtIHZpZihtb2RlbCkKCiMgQ29udmVydCB0aGUgVklGIHZhbHVlcyB0byBhIGRhdGEgZnJhbWUKdmlmX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2aWZfdmFsdWVzKSwKICAgICAgICAgICAgICAgICAgICAgVklGID0gYXMudmVjdG9yKHZpZl92YWx1ZXMpKQoKIyBEaXNwbGF5IHRoZSBWSUYgdGFibGUgdXNpbmcga25pdHI6OmthYmxlCmtuaXRyOjprYWJsZSh2aWZfZGYsIGNhcHRpb24gPSAiVmFyaWFuY2UgSW5mbGF0aW9uIEZhY3RvcnMgZm9yIHRoZSBNb2RlbCIpCmBgYAoKIyMjIFNjYXR0ZXJwbG90CgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Niwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbG9uZ2VyPC1ob3VzZSAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGMoCiAgICAiaGVhdGVkYXJlYSIsCiAgICAiYWdlIiwKICAgICJjcmltZSIsCiAgICAidHJhbnNpdCIpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJWYXJpYWJsZSIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJWYWx1ZSIpJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpCgpnZ3Bsb3QobG9uZ2VyLGFlcyh4ID0gVmFsdWUsIHkgPSBwcmljZSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gImJsYWNrIiwgc2l6ZT0gMC40KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAicmVkIiwgc2UgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiBWYXJpYWJsZSwgc2NhbGVzID0gImZyZWUiLCBsYWJlbGxlciA9IGFzX2xhYmVsbGVyKGMoImhlYXRlZGFyZWEiID0gIkhlYXRlZCBBcmVhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImFnZSIgPSAiQWdlIG9mIHRoZSBIb3VzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjcmltZSIgPSAiTnVtYmVyIG9mIENyaW1lIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRyYW5zaXQiID0gIlRyYW5zaXQgQ292ZXJhZ2UiCiAgKSkpICArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpKSArCiAgbGFicyh0aXRsZSA9ICJTY2F0dGVyIFBsb3RzIG9mIERlcGVuZGVudCBWYXJpYWJsZSB2cy4gUHJlZGljdG9ycyIsCiAgICAgICB4ID0gIlZhbHVlIiwKICAgICAgIHkgPSAiSG91c2UgcHJpY2UiKQoKYGBgCgojIyBTdGVwIDQ6IE1hcHBpbmcKCiMjIyBEZXBlbmRlbnQgVmFyaWFibGUtIEhvdXNpbmcgcHJpY2UKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTAsIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KbWFwPC1zdF9qb2luKGhvdXNlLCBpbmNvbWUpJT4lCiAgZ3JvdXBfYnkoR0VPSUQpJT4lCiAgc3VtbWFyaXNlKHByaWNlPW1lYW4ocHJpY2UpKSU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKQoKbWFwPC0gbGVmdF9qb2luKGluY29tZSwgbWFwLCBieT0iR0VPSUQiKSU+JQogIGZpbHRlcighaXMubmEocHJpY2UpKQoKCmdncGxvdChtYXApICsKICBnZW9tX3NmKGFlcyhmaWxsPSBxNShwcmljZSkpLCBjb2xvcj0nd2hpdGUnKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZmxhdHJlZHM1LAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSByb3VuZChhcy5udW1lcmljKHFCcihtYXAsICdwcmljZScpKSwgMCksCiAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAnSG91c2luZyBwcmljZScpICsKICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgSG91c2UgUHJpY2UgYnkgQ2Vuc3VzIEJsb2NrIEdyb3VwIiwKICAgICAgIGZpbGwgPSAiUHJpY2UiKSArCiAgICB0aGVtZSgKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCwgaGp1c3Q9IDAuNSxmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGwgPSBOQSwgc2l6ZSA9IDAuOCkpCmBgYAoKIyMjIEtleSBwcmVkaWN0b3JzCgoKCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQpRMTwtZ2dwbG90KG1hcCkgKwogIGdlb21fc2YoYWVzKGZpbGw9IHE1KGluY29tZSkpLCBjb2xvcj0nd2hpdGUnKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZmxhdHJlZHM1LAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSByb3VuZChhcy5udW1lcmljKHFCcihtYXAsICdpbmNvbWUnKSksIDApLAogICAgICAgICAgICAgICAgICAgICBuYW1lID0gJ0luY29tZScpICsKICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgSG91c2Vob2xkIEluY29tZSBieSBDZW5zdXMgQmxvY2sgR3JvdXAiLAogICAgICAgZmlsbCA9ICJJbmNvbWUiKSArCiAgICB0aGVtZSgKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNCksCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA1KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUsIGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGhqdXN0PSAwLjUsZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsID0gTkEsIHNpemUgPSAwLjgpKQoKUTI8LWdncGxvdCh0cmVlKSArCiAgZ2VvbV9zZihhZXMoZmlsbD0gcTUoY2Fub3B5KSksIGNvbG9yPSd3aGl0ZScpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBmbGF0cmVkczUsCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGZ1bmN0aW9uKHgpIHJvdW5kKGFzLm51bWVyaWMocUJyKHRyZWUsICdjYW5vcHknKSksIDApLAogICAgICAgICAgICAgICAgICAgICBuYW1lID0gJyUgb2YgVHJlZSBDb3ZlcmFnZScpICsKICBsYWJzKHRpdGxlID0gIlRyZWUgQ2Fub3B5IGJ5IE5laWdoYm9yaG9vZCIsCiAgICAgICBmaWxsID0gIiUgb2YgVHJlZSBDb3ZlcmFnZSIpICsKICAgIHRoZW1lKAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPTQpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNSksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA1LCBmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBoanVzdD0gMC41LGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbCA9IE5BLCBzaXplID0gMC44KSkKCnJlY3JlYXRpb248LXJlY3JlYXRpb24lPiUgZmlsdGVyKCFpcy5uYShyZWNyZWF0aW9uKSkKUTM8LWdncGxvdChyZWNyZWF0aW9uKSArCiAgZ2VvbV9zZihhZXMoZmlsbD0gcTUocmVjcmVhdGlvbikpLCBjb2xvcj0nd2hpdGUnKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZmxhdHJlZHM1LAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSByb3VuZChhcy5udW1lcmljKHFCcih0cmVlLCAnY2Fub3B5JykpLCAwKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICclIG9mIFJlY3JlYXRpb24gQ292ZXJhZ2UnKSArCiAgbGFicyh0aXRsZSA9ICJSZWNyZWF0aW9uIGZhY2lsaXRpZXMgY292ZXJhZ2UgYnkgTmVpZ2hib3Job29kIiwKICAgICAgIGZpbGwgPSAiJSBvZiBSZWNyZWF0aW9uYWwgZmFjaWxpdGllcyBDb3ZlcmFnZSIpICsKICAgIHRoZW1lKAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA0KSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNSwgZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgaGp1c3Q9IDAuNSxmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGwgPSBOQSwgc2l6ZSA9IDAuOCkpCgp0cmFuc2l0PC10cmFuc2l0JT4lIGZpbHRlcighaXMubmEodHJhbnNpdCkpCgpRNDwtZ2dwbG90KHRyYW5zaXQpICsKICBnZW9tX3NmKGFlcyhmaWxsPSBxNSh0cmFuc2l0KSksIGNvbG9yPSd3aGl0ZScpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBmbGF0cmVkczUsCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGZ1bmN0aW9uKHgpIHJvdW5kKGFzLm51bWVyaWMocUJyKHRyYW5zaXQsICd0cmFuc2l0JykpLCAwKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICclIG9mIFRyYW5zaXQgQ292ZXJhZ2UnKSArCiAgbGFicyh0aXRsZSA9ICJQdWJsaWMgVHJhbnNpdCBjb3ZlcmFnZSBieSBOZWlnaGJvcmhvb2QiLAogICAgICAgZmlsbCA9ICIlIG9mIFB1YmxpYyBUcmFuc2l0IENvdmVyYWdlIikgKwogICAgdGhlbWUoCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDQpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNSksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA1LCBmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBoanVzdD0gMC41LGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbCA9IE5BLCBzaXplID0gMC44KSkKCiMgQXJyYW5nZSB0aGUgcGxvdHMgaW4gYSAyeDIgZ3JpZApncmlkLmFycmFuZ2UoUTEsIFEyLCBRMywgUTQsIG5jb2wgPSAyKQoKYGBgCgojIE1vZGVsIEJ1aWxkaW5nICYgRXZhbHVhdGlvbgoKIyMgVHJhaW4tVGVzdCBTcGxpdAoKSW4gdGhpcyBtb2RlbCBidWlsZGluZyBwcm9jZXNzLCBJIHNwaWx0IHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhc2V0cyBiYXNlZCBvbiA4MCUgdHJhaW5pbmcgYW5kIDIwJSB0ZXN0aW5nIHRvIGVuc3VyZSB0aGUgbW9kZWwgaGF2ZSBhIGJldHRlciBwcmVkaWN0aXZpdHkKCmBgYHtyfQojIFNldCB0aGUgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnNldC5zZWVkKDEyMykKCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oaG91c2UkcHJpY2UsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKCnRyYWluIDwtIGhvdXNlW3RyYWluX2luZGV4LCBdCnRlc3QgPC0gaG91c2VbLXRyYWluX2luZGV4LCBdCmBgYAoKIyMgTW9kZWwgQnVpbGRpbmcgYW5kIEV2YWx1YXRpb24KCiMjIyBJbml0aWFsIG1vZGVsCmBgYHtyfQptb2RlbDEgPC0gbG0obG9ncHJpY2UgfiBoYWxmYmF0aHMgKyBoZWF0ZWRhcmVhICsgbnVtZmlyZXBsYSArIGJsZGdncmFkZSArIHVuaXRzICsgYWdlKyBpbmNvbWUgKyBjcmltZSArIHRyYW5zaXQgKyBjYW5vcHkgKyByZWNyZWF0aW9uLCBkYXRhID0gdHJhaW4pCgp0ZXN0PC10ZXN0JT4lCiAgbXV0YXRlKHByZWRpY3Rpb25zID0gcHJlZGljdChtb2RlbDEsIG5ld2RhdGEgPSB0ZXN0KSwKICAgICAgICAgYWN0dWFsPSBsb2dwcmljZSwKICAgICAgICAgZXJyb3I9IGFjdHVhbCAtIHByZWRpY3Rpb25zLAogICAgICAgICBhYnNlcnJvcj0gYWJzKGVycm9yKSwKICAgICAgICAgYXBlPShhYnMoYWN0dWFsLXByZWRpY3Rpb25zKSkgLyBhY3R1YWwpCgojIENhbGN1bGF0ZSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpCm1hZSA8LSBtZWFuKHRlc3QkYWJzZXJyb3IpCgojIENhbGN1bGF0ZSBSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkKcm1zZSA8LSBzcXJ0KG1lYW4oKHRlc3QkYWN0dWFsIC0gdGVzdCRwcmVkaWN0aW9ucyleMikpCgojIENhbGN1bGF0ZSBSLXNxdWFyZWQgZm9yIHRoZSB0ZXN0IHNldApzc2UgPC0gc3VtKCh0ZXN0JGFjdHVhbCAtIHRlc3QkcHJlZGljdGlvbnMpXjIpCnNzdCA8LSBzdW0oKHRlc3QkYWN0dWFsIC0gbWVhbih0ZXN0JGFjdHVhbCkpXjIpCnIyIDwtIDEgLSBzc2Uvc3N0CgojIFByaW50IHRoZSByZXN1bHRzCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICBNZXRyaWMgPSBjKCJNZWFuIEFic29sdXRlIEVycm9yIChNQUUpIiwgIlJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSIsICJUZXN0IFItc3F1YXJlZCIpLAogIFZhbHVlID0gYyhyb3VuZChtYWUsIDUpLCByb3VuZChybXNlLCA1KSwgcm91bmQocjIsIDUpKQopCgojIFByaW50IHJlc3VsdHMgdXNpbmcga2FibGUKa2FibGUocmVzdWx0cywgY2FwdGlvbiA9ICJNb2RlbCBQZXJmb3JtYW5jZSBNZXRyaWNzIikKYGBgCiMjIyBJbmNsdWRlIHRoZSBkdW1teSB2YXJpYWJsZXMKYGBge3J9Cm1vZGVsMiA8LSBsbShsb2dwcmljZSB+IGhhbGZiYXRocyArIGhlYXRlZGFyZWEgKyBudW1maXJlcGxhICsgYmxkZ2dyYWRlICsgdW5pdHMgKyBhZ2UgKyBhY19kdW1teSArIGhlYXRfZHVtbXkgKyBpbmNvbWUgKyBjcmltZSArIHRyYW5zaXQgKyBjYW5vcHkgKyByZWNyZWF0aW9uLCBkYXRhID0gdHJhaW4pCgp0ZXN0PC10ZXN0JT4lCiAgbXV0YXRlKHByZWRpY3Rpb25zID0gcHJlZGljdChtb2RlbDIsIG5ld2RhdGEgPSB0ZXN0KSwKICAgICAgICAgYWN0dWFsPSBsb2dwcmljZSwKICAgICAgICAgZXJyb3I9IGFjdHVhbCAtIHByZWRpY3Rpb25zLAogICAgICAgICBhYnNlcnJvcj0gYWJzKGVycm9yKSwKICAgICAgICAgYXBlPShhYnMoYWN0dWFsLXByZWRpY3Rpb25zKSkgLyBhY3R1YWwpCgojIENhbGN1bGF0ZSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpCm1hZSA8LSBtZWFuKHRlc3QkYWJzZXJyb3IpCgojIENhbGN1bGF0ZSBSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkKcm1zZSA8LSBzcXJ0KG1lYW4oKHRlc3QkYWN0dWFsIC0gdGVzdCRwcmVkaWN0aW9ucyleMikpCgojIENhbGN1bGF0ZSBSLXNxdWFyZWQgZm9yIHRoZSB0ZXN0IHNldApzc2UgPC0gc3VtKCh0ZXN0JGFjdHVhbCAtIHRlc3QkcHJlZGljdGlvbnMpXjIpCnNzdCA8LSBzdW0oKHRlc3QkYWN0dWFsIC0gbWVhbih0ZXN0JGFjdHVhbCkpXjIpCnIyIDwtIDEgLSBzc2Uvc3N0CgojIFByaW50IHRoZSByZXN1bHRzCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICBNZXRyaWMgPSBjKCJNZWFuIEFic29sdXRlIEVycm9yIChNQUUpIiwgIlJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSIsICJUZXN0IFItc3F1YXJlZCIpLAogIFZhbHVlID0gYyhyb3VuZChtYWUsIDUpLCByb3VuZChybXNlLCA1KSwgcm91bmQocjIsIDUpKQopCgojIFByaW50IHJlc3VsdHMgdXNpbmcga2FibGUKa2FibGUocmVzdWx0cywgY2FwdGlvbiA9ICJNb2RlbCBQZXJmb3JtYW5jZSBNZXRyaWNzIikKYGBgCiMjIyBJbmNsdWRlIHRoZSBDYXRlZ29yaWNhbCBWYXJpYWJsZXMKYGBge3J9Cm1vZGVsMyA8LSBsbShsb2dwcmljZSB+IGhhbGZiYXRocyArIGhlYXRlZGFyZWEgKyBudW1maXJlcGxhICsgYmxkZ2dyYWRlICsgdW5pdHMgKyBhZ2UrIGluY29tZSArIGNyaW1lICsgdHJhbnNpdCArIGNhbm9weSArIHJlY3JlYXRpb24gKyB0eXBlLCBkYXRhID0gdHJhaW4pCgp0ZXN0PC10ZXN0JT4lCiAgbXV0YXRlKHByZWRpY3Rpb25zID0gcHJlZGljdChtb2RlbDMsIG5ld2RhdGEgPSB0ZXN0KSwKICAgICAgICAgYWN0dWFsPSBsb2dwcmljZSwKICAgICAgICAgZXJyb3I9IGFjdHVhbCAtIHByZWRpY3Rpb25zLAogICAgICAgICBhYnNlcnJvcj0gYWJzKGVycm9yKSwKICAgICAgICAgYXBlPShhYnMoYWN0dWFsLXByZWRpY3Rpb25zKSkgLyBhY3R1YWwpCgojIENhbGN1bGF0ZSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpCm1hZSA8LSBtZWFuKHRlc3QkYWJzZXJyb3IpCgojIENhbGN1bGF0ZSBSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkKcm1zZSA8LSBzcXJ0KG1lYW4oKHRlc3QkYWN0dWFsIC0gdGVzdCRwcmVkaWN0aW9ucyleMikpCgojIENhbGN1bGF0ZSBSLXNxdWFyZWQgZm9yIHRoZSB0ZXN0IHNldApzc2UgPC0gc3VtKCh0ZXN0JGFjdHVhbCAtIHRlc3QkcHJlZGljdGlvbnMpXjIpCnNzdCA8LSBzdW0oKHRlc3QkYWN0dWFsIC0gbWVhbih0ZXN0JGFjdHVhbCkpXjIpCnIyIDwtIDEgLSBzc2Uvc3N0CgojIFByaW50IHRoZSByZXN1bHRzCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICBNZXRyaWMgPSBjKCJNZWFuIEFic29sdXRlIEVycm9yIChNQUUpIiwgIlJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSIsICJUZXN0IFItc3F1YXJlZCIpLAogIFZhbHVlID0gYyhyb3VuZChtYWUsIDUpLCByb3VuZChybXNlLCA1KSwgcm91bmQocjIsIDUpKQopCgojIFByaW50IHJlc3VsdHMgdXNpbmcga2FibGUKa2FibGUocmVzdWx0cywgY2FwdGlvbiA9ICJNb2RlbCBQZXJmb3JtYW5jZSBNZXRyaWNzIikKYGBgCiMjIyBGaW5hbCBtb2RlbApgYGB7cn0KbW9kZWw0IDwtIGxtKGxvZ3ByaWNlIH4gIGhlYXRlZGFyZWEgKyBudW1maXJlcGxhKyBibGRnZ3JhZGUgKyB1bml0cyArIGFnZSsgaW5jb21lICsgY3JpbWUgKyB0cmFuc2l0ICsgY2Fub3B5ICsgdHlwZSsgYWNfZHVtbXkgKyBoZWF0X2R1bW15LCBkYXRhID0gdHJhaW4pCnN1bW1hcnkobW9kZWw0KQpgYGAKYGBge3J9CnRlc3Q8LXRlc3QlPiUKICBtdXRhdGUocHJlZGljdGlvbnMgPSBwcmVkaWN0KG1vZGVsNCwgbmV3ZGF0YSA9IHRlc3QpLAogICAgICAgICBhY3R1YWw9IGxvZ3ByaWNlLAogICAgICAgICBlcnJvcj0gYWN0dWFsIC0gcHJlZGljdGlvbnMsCiAgICAgICAgIGFic2Vycm9yPSBhYnMoZXJyb3IpLAogICAgICAgICBhcGU9KGFicyhhY3R1YWwtcHJlZGljdGlvbnMpKSAvIGFjdHVhbCkKCiMgQ2FsY3VsYXRlIE1lYW4gQWJzb2x1dGUgRXJyb3IgKE1BRSkKbWFlIDwtIG1lYW4odGVzdCRhYnNlcnJvcikKCiMgQ2FsY3VsYXRlIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKQpybXNlIDwtIHNxcnQobWVhbigodGVzdCRhY3R1YWwgLSB0ZXN0JHByZWRpY3Rpb25zKV4yKSkKCiMgQ2FsY3VsYXRlIFItc3F1YXJlZCBmb3IgdGhlIHRlc3Qgc2V0CnNzZSA8LSBzdW0oKHRlc3QkYWN0dWFsIC0gdGVzdCRwcmVkaWN0aW9ucyleMikKc3N0IDwtIHN1bSgodGVzdCRhY3R1YWwgLSBtZWFuKHRlc3QkYWN0dWFsKSleMikKcjIgPC0gMSAtIHNzZS9zc3QKCiMgUHJpbnQgdGhlIHJlc3VsdHMKcmVzdWx0cyA8LSBkYXRhLmZyYW1lKAogIE1ldHJpYyA9IGMoIk1lYW4gQWJzb2x1dGUgRXJyb3IgKE1BRSkiLCAiUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IgKFJNU0UpIiwgIlRlc3QgUi1zcXVhcmVkIiksCiAgVmFsdWUgPSBjKHJvdW5kKG1hZSwgNSksIHJvdW5kKHJtc2UsIDUpLCByb3VuZChyMiwgNSkpCikKCiMgUHJpbnQgcmVzdWx0cyB1c2luZyBrYWJsZQprYWJsZShyZXN1bHRzLCBjYXB0aW9uID0gIk1vZGVsIFBlcmZvcm1hbmNlIE1ldHJpY3MiKQpgYGAKCgojIyMgbW9kZWwgZGlhZ25vc3RpY3MKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9CnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3QobW9kZWw0KQpgYGAKCioqQ29tcGFyZSB3aXRoIG5vbiBsb2cgdHJhbnNmb3JtZWQgcHJpY2UgbW9kZWwqKgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQptb2RlbDUgPC0gbG0ocHJpY2UgfiAgaGVhdGVkYXJlYSArIG51bWZpcmVwbGErIGJsZGdncmFkZSArIHVuaXRzICsgYWdlKyBpbmNvbWUgKyBjcmltZSArIHRyYW5zaXQgKyBjYW5vcHkgKyB0eXBlKyBhY19kdW1teSArIGhlYXRfZHVtbXksIGRhdGEgPSB0cmFpbikKCnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3QobW9kZWw1KQpgYGAKCiMgU3BhdGlhbCBBbmFseXNpcyAmIFJlc2lkdWFsIERpYWdub3N0aWNzCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbW9kZWxfZmluYWw8LWxtKGxvZ3ByaWNlIH4gIGhlYXRlZGFyZWEgKyBudW1maXJlcGxhKyBibGRnZ3JhZGUgKyB1bml0cyArIGFnZSsgaW5jb21lICsgY3JpbWUgKyB0cmFuc2l0ICsgY2Fub3B5ICsgdHlwZSsgYWNfZHVtbXkgKyBoZWF0X2R1bW15LCBkYXRhID0gaG91c2UpCgpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbF9maW5hbCkKcmVzaWR1YWxzIDwtIHJlc2lkdWFscyhtb2RlbF9maW5hbCkKCmhvdXNlPC0gaG91c2UgJT4lCiAgbXV0YXRlKGZpdHRlZCA9IGZpdHRlZF92YWx1ZXMsCiAgICAgICAgIHJlc2lkdWFscyA9IHJlc2lkdWFscykKCmdncGxvdChob3VzZSwgYWVzKHggPSBmaXR0ZWQsIHkgPSBsb2dwcmljZSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuNSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gInJlZCIsIHNlID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZCBWYWx1ZXMgdnMuIE9ic2VydmVkIFZhbHVlcyIsCiAgICAgICB4ID0gIk9ic2VydmVkIFZhbHVlcyIsCiAgICAgICB5ID0gIlByZWRpY3RlZCBWYWx1ZXMiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwKICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkKCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMCwgbWVhc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlc2lkdWFsX21hcDwtc3Rfam9pbihpbmNvbWUsIGhvdXNlKSU+JQogIGdyb3VwX2J5KEdFT0lEKSU+JQogIHN1bW1hcmlzZShyZXNpZHVhbD1tZWFuKHJlc2lkdWFscykpJT4lCiAgZmlsdGVyKCFpcy5uYShyZXNpZHVhbCkpCgpnZ3Bsb3QocmVzaWR1YWxfbWFwKSArCiAgZ2VvbV9zZihhZXMoZmlsbD0gcTUocmVzaWR1YWwpKSwgY29sb3I9J3doaXRlJykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGZsYXRyZWRzNSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gZnVuY3Rpb24oeCkgcm91bmQoYXMubnVtZXJpYyhxQnIocmVzaWR1YWxfbWFwLCAncmVzaWR1YWwnKSksIDYpLAogICAgICAgICAgICAgICAgICAgICBuYW1lID0gJ1Jlc2lkdWFscycpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdWFscyBTcGF0aWFsIERpc3RyaWJ1dGlvbiIsCiAgICAgICBmaWxsID0gIlJlc2lkdWFscyIpICsKICAgIHRoZW1lKAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPTgpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCwgZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCwgaGp1c3Q9IDAuNSxmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGwgPSBOQSwgc2l6ZSA9IDAuOCkpCiAgCmBgYApgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgRXh0cmFjdCB0aGUgY29vcmRpbmF0ZXMgZnJvbSB0aGUgc3BhdGlhbCBkYXRhZnJhbWUKY29vcmRzIDwtIHN0X2Nvb3JkaW5hdGVzKHRlc3QpIAoKIyBDcmVhdGUgYSBuZWlnaGJvciBsaXN0IHVzaW5nIGstbmVhcmVzdCBuZWlnaGJvcnMgKEtOTikgd2l0aCBrPTUKbmVpZ2hib3JMaXN0IDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3JkcywgNSkpCgojIENvbnZlcnQgdGhlIG5laWdoYm9yIGxpc3QgdG8gYSBzcGF0aWFsIHdlaWdodHMgbWF0cml4CnNwYXRpYWxXZWlnaHRzIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdCwgc3R5bGU9IlciKQoKbW9yYW4gPC0gbW9yYW4ubWModGVzdCRlcnJvciwgc3BhdGlhbFdlaWdodHMsIG5zaW0gPSA5OTkpCgpnZ3Bsb3QoYXMuZGF0YS5mcmFtZShtb3JhbiRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuJHJlc1tjKDE6OTk5KV0pKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjAxKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1vcmFuJHN0YXRpc3RpYyksIGNvbG91ciA9ICIjRkE3ODAwIixzaXplPTEpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygtMSwgMSkpICsKICBsYWJzKHRpdGxlPSJPYnNlcnZlZCBhbmQgcGVybXV0ZWQgTW9yYW4ncyBJIiwKICAgICAgIHN1YnRpdGxlPSAiT2JzZXJ2ZWQgTW9yYW4ncyBJIGluIG9yYW5nZSIsCiAgICAgICB4PSJNb3JhbidzIEkiLAogICAgICAgeT0iQ291bnQiKSArCiAgdGhlbWVfbWluaW1hbCgpCgpgYGAKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIEV4dHJhY3QgdGhlIGNvb3JkaW5hdGVzIGZyb20gdGhlIHNwYXRpYWwgZGF0YWZyYW1lCmNvb3JkcyA8LSBzdF9jb29yZGluYXRlcyhob3VzZSkgCgojIENyZWF0ZSBhIG5laWdoYm9yIGxpc3QgdXNpbmcgay1uZWFyZXN0IG5laWdoYm9ycyAoS05OKSB3aXRoIGs9NQpuZWlnaGJvckxpc3QgPC0ga25uMm5iKGtuZWFybmVpZ2goY29vcmRzLCA1KSkKCiMgQ29udmVydCB0aGUgbmVpZ2hib3IgbGlzdCB0byBhIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXgKc3BhdGlhbFdlaWdodHMgPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LCBzdHlsZT0iVyIpCgptb3JhbiA8LSBtb3Jhbi5tYyhob3VzZSRyZXNpZHVhbHMsIHNwYXRpYWxXZWlnaHRzLCBuc2ltID0gOTk5KQoKZ2dwbG90KGFzLmRhdGEuZnJhbWUobW9yYW4kcmVzW2MoMTo5OTkpXSksIGFlcyhtb3JhbiRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhbiRzdGF0aXN0aWMpLCBjb2xvdXIgPSAiI0ZBNzgwMCIsc2l6ZT0xKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoLTEsIDEpKSArCiAgbGFicyh0aXRsZT0iT2JzZXJ2ZWQgYW5kIHBlcm11dGVkIE1vcmFuJ3MgSSIsCiAgICAgICBzdWJ0aXRsZT0gIk9ic2VydmVkIE1vcmFuJ3MgSSBpbiBvcmFuZ2UiLAogICAgICAgeD0iTW9yYW4ncyBJIiwKICAgICAgIHk9IkNvdW50IikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMgQ29uY2x1c2lvbgo=